Hello, I am trying to create a vex::task object using a non-static function that belongs to a class. However, it is not working because the constructor expects a function with no parameters, but in C++ all non-static class functions have a hidden parameter to pass in a pointer to the object that is being used. So even though I added no parameters, there is still a hidden parameter that changes the method signature. Is there a way to get around this? I noticed this other constructor for vex::task:
vex::task::task(int(*)(void *) callback, void * arg )
which has a void pointer argument that seems to be used for passing an argument into the function? Can I use this constructor to send a pointer to the object, as the function is expecting? I tried this and the closest I got was this:
screen.screenTask = vex::task((int(*)(void*))screen.screenLoop(), (void*)&screen);
This does compile fine, but it seems that by referencing screen.screenLoop() with the parenthesis, it ends up calling the function in the main task, causing the main task to get stuck. It does not compile if I remove the parenthesis. I would appreciate any ideas!
yea, you have to use a static member function as a callback for tasks (and threads). However, you can pass the current class instance as a parameter to the callback and use that to do instance specific things. Here’s a simple example.
/*----------------------------------------------------------------------------*/
/* */
/* Module: main.cpp */
/* Author: james */
/* Created: Sat Jul 04 2020 */
/* Description: V5 project */
/* */
/*----------------------------------------------------------------------------*/
// ---- START VEXCODE CONFIGURED DEVICES ----
// ---- END VEXCODE CONFIGURED DEVICES ----
#include "vex.h"
using namespace vex;
class foo {
public:
vex::thread t;
int value;
int loopDelay;
int count;
foo( int val, int delay ) : value(val), loopDelay(delay) {
count = 0;
t = vex::thread( foo::bar, static_cast<void *>(this) );
}
~foo() {}
static int bar( void *arg ) {
if( arg == NULL )
return 0;
foo *instance = static_cast<foo *>(arg);
while(1) {
printf("this thread %d %d\n", instance->value, instance->count++ );
this_thread::sleep_for(instance->loopDelay);
}
return 0;
}
};
int main() {
foo( 23, 1000 );
foo( 12, 1200 );
}
Thanks for the help. It worked, though it is a bit unfortunate that this doesn’t work with member functions
JPearman is on point as usual, but I would like to point out this is a question very well asked.
This is kind of a hack solution, but using a lambda expression seems to work
screen.screenTask = vex::task([](){
screen.screenLoop();
return 0;
});
Not entirely sure whether this is the same solution as @jpearman pointed out, but couldn’t you just make a static function that all it does is call the function from the class instance, and then use that static function as the task callback?
Edit: the code would look something like this:
class joeyHasBetterClassNamesThanJPearman
{
//public identifier
public joeyHasBetterClassNamesThanJPearman(/*parameters*/)
{
//initialization
}
//code you want to run in the task
public void hiddenParametersAreDumb()
{
//code that runs with no parameters
}
}
//your instance of the class
joeyHasBetterClassNamesThanJPearman someInstance = new joeyHasBetterClassNamesThanJPearman(/*parameters*/);
//static function that can run as a task
public static void noMoreHiddenParamerers()
{
someInstance.hiddenParametersAreDumb();
}
//your task
vex::task(noMoreHiddenParameters);
see this.
http://p-nand-q.com/programming/cplusplus/using_member_functions_with_c_function_pointers.html
vexos is written entirely in C.
That wpuld work with a few modifications (you dont need the static modifier for non-member functions) but its less readable.
Technically true, but the goal here is too use the function as a task, and as (@)jpearman pointed out,
Also, sorry if some of the example code I wrote was incorrect, I’m much more used to the syntax of C# than C++.
If additional parameters are constant then you can use templates as an alternative solution.
int threadWorker(void *arg, int a, int b)
{
printf("thread started with arg=%p, a=%d, b=%d\n", arg, a, b);
while(1)
{
// do something useful
this_thread::sleep_for(20);
}
return 0;
}
template <int a, int b>
int threadWrapper(void *arg)
{
return threadWorker(arg, a, b);
}
int main()
{
int arg1=1, arg2=2;
vex::thread( threadWrapper<23,1000>, &arg1 );
vex::thread( threadWrapper<12,1200>, &arg2 );
}
For each set of calling parameters, compiler will generate a custom threadWrapper function body, which becomes thread entry point and will call your threadWorker() function after thread is created. That should not take much space and you can still pass one non-hardcoded argument arg
.
Even though concept of C++ template may be new for beginners, I feel like this is a preferred method, because it explicitly shows vex::thread()
call every time when a new thread is created.
However, if you need to pass additional non constant parameters that are not known at the compile time or return values from the thread then you will need to do it with classes.
yes, there are other options.
It’s actually easier in Python.
# ------------------------------------------
#
# Project: VEXcode Project
# Author: VEX
# Created:
# Description: VEXcode V5 Python Project
#
# ------------------------------------------
# Library imports
from vex import *
# Begin project code
class foo:
def __init__(self, value, delay=50):
self.value = value
self.delay = delay
self.count = 0
self.t = Thread(self.bar)
def bar(self):
while True:
print("thread ", self.value, " ", self.count)
self.count += 1
wait(self.delay, MSEC)
foo(12, 1000)
foo(23, 1200)
Does this work the same way for PROS threading? I have been trying do this using pros::c::task_create and I keep getting a data abort exception.