PROS Task On Member Functions

Hi everyone, I’m fairly new to C and C++, and particularly PROS, and I seem to have run into somewhat of a brick wall while trying to add an abstraction layer between the logic and hardware within my team’s code. My idea is to have a drivetrain class containing all the functions required for autonomous driving (drive, strafe, turn, etc.), as well as a completely self-contained controller loop. In order for the program to continue after the controller loop has started, I need to run the loop in a separate thread, which I believe should be done using pros::Task. Because the controller loop requires the accessing of a lot of private variables, I need it to be a member function of the class. However, when instantiating a Task with such a member variable, I get a number of errors, which are (unfortunately) above my limited debugging skills.

Here is a minimal reproducible example of my code:

#include "main.h"
#include "string"

class DriveTrain
{
private:
    std::string helloWorld {"Hello, World!"};
public:
    DriveTrain()
    {
        pros::Task helloWorldTask(helloWorldPrint);
    }

    void helloWorldPrint(void * param) {
        std::cout << helloWorld << std::endl;
    }
};

void initialize() {
    DriveTrain base;
}

This produces the following error:

Compiled src/main.cpp [WARNINGS]
src/main.cpp: In constructor 'DriveTrain::DriveTrain()':
src/main.cpp:11:35: error: invalid use of non-static member function 'void DriveTrain::helloWorldPrint(void*)'
   11 |         pros::Task helloWorldTask(helloWorldPrint);
      |                                   ^~~~~~~~~~~~~~~
src/main.cpp:14:10: note: declared here
   14 |     void helloWorldPrint(void * param) {
      |          ^~~~~~~~~~~~~~~
Adding timestamp [OK]
Linking hot project with ./bin/cold.package.elf and libc,libm,libpros,okapilib [OK]
Section sizes:
   text    data     bss   total     hex filename
540.00B   4.00B  46.02MB  46.02MB 2e03a21 bin/hot.package.elf
Creating bin/hot.package.bin for VEX EDR V5 [DONE]
Capturing metadata for PROS Editor...

When adding a static keyword to the front of the helloWorldPrint function, I instead get the following error:

Compiled src/main.cpp [WARNINGS]
src/main.cpp: In static member function 'static void DriveTrain::helloWorldPrint(void*)':
src/main.cpp:15:22: error: invalid use of member 'DriveTrain::helloWorld' in static member function
   15 |         std::cout << helloWorld << std::endl;
      |                      ^~~~~~~~~~
src/main.cpp:7:17: note: declared here
    7 |     std::string helloWorld {"Hello, World!"};
      |                 ^~~~~~~~~~
Adding timestamp [OK]
Linking hot project with ./bin/cold.package.elf and libc,libm,libpros,okapilib [OK]
Section sizes:
   text    data     bss   total     hex filename
540.00B   4.00B  46.02MB  46.02MB 2e03a21 bin/hot.package.elf
Creating bin/hot.package.bin for VEX EDR V5 [DONE]
Capturing metadata for PROS Editor...

Sorry about the long post, any help is much appreciated. Thanks!

Can you try taking out the void * parameter and rebuilding? If it still doesn’t build, add parenthesis to when you call “hello world print”

Taking out the void * parameter resulted in the same invalid use of non-static member function 'void DriveTrain::helloWorldPrint()' error.

Adding the parentheses resulted in the following error:

Compiled src/main.cpp [WARNINGS]
src/main.cpp: In constructor 'DriveTrain::DriveTrain()':
src/main.cpp:11:51: error: no matching function for call to 'DriveTrain::helloWorldPrint()'
   11 |         pros::Task helloWorldTask(helloWorldPrint());
      |                                                   ^
src/main.cpp:14:10: note: candidate: 'void DriveTrain::helloWorldPrint(void*)'
   14 |     void helloWorldPrint(void *param) {
      |          ^~~~~~~~~~~~~~~
src/main.cpp:14:10: note:   candidate expects 1 argument, 0 provided

I may very well be wrong, but to the best of my knowledge, the last error is caused by the fact that adding the parentheses passes the result of the function to the Task object. However, like I said, I’m no expert on C or C++, so there is a decent chance I’m doing something completely wrong.

I pass the baton onto someone else, I got no clue since I have yet to begin my training debugging C++

1 Like

You can’t call a function that uses parameters from a task. When you call a function with a task, you can’t pass any parameters, so apparently they don’t let you call functions that take parameters from tasks. try removing

Okay, actually I remembered that in VEXCode, functions called from tasks have to return something. Try:

int helloWorldPrint() {
  std::cout << helloWorld << std::endl;
  return 0
}
1 Like

Thanks for the reply! Unfortunately, changing the function to return an int resulted in the following error:

Compiled src/main.cpp [WARNINGS]
src/main.cpp: In constructor 'DriveTrain::DriveTrain()':
src/main.cpp:11:35: error: invalid use of non-static member function 'int DriveTrain::helloWorldPrint(void*)'
   11 |         pros::Task helloWorldTask(helloWorldPrint);
      |                                   ^~~~~~~~~~~~~~~
src/main.cpp:14:9: note: declared here
   14 |     int helloWorldPrint(void *param) {
      |         ^~~~~~~~~~~~~~~

This is the error. Google it.

Because you initialize String helloWorld in the class, it will be created when
DriveTriain object is created. When you use static, there is no this pointer point to your helloWorld variable. My suggestion is, don’t use class here or you can initialize helloWorld as a global variable.

4 Likes

Great question.

You can’t natively use a class member function as the task function. If I recall, this is because the member functions have a parameter that implicitly points to itself, which clashes with the pros task declaration. However, there are workarounds for this.

The first method is to use a lambda function to wrap around the member function:

void MyClass::startTask(){
    pros::Task my_task([this] { 
        this->task_loop(); 
        pros::delay(10);
    } );
}

The second method is to define a static function (often called a trampoline) which takes a pointer to the class instance. The function then runs the specific loop by casting the instance pointer and then calling the loop.

void ClassName::trampoline(void* iparam){
    if(iparam){
        ClassName* that = static_cast<ClassName*>(iparam);
        that->task_loop();
        pros::delay(10);
    }
}

void ClassName::startTask(){
    pros::Task my_task(trampoline, this, "My_Task"));
}

To everyone above: if you don’t know the answer, don’t reply.

12 Likes