c++lambdac++17std-function

Assigning a class member function to a variable of type std::function<void(void *)>


I'm relatively new to C++, please forgive some imperfections in my formulation.

Summary:

Basically I have a class member function and a variable of type std::function<void(void *)>. I want to assign my class member function to this variable.

Using a lambda expression I get this to work:

typedef std::function<void(void *)> Task;
Task task = [&](void *context) { ClassMemberFunction(context); };

But this lambda is capturing the class itself as reference (because of [&]) and this seems unnecessary as that is what the context argument is for.

Is there a better/cleaner way to achieve this?


TLDR, complete picture:

I have an object that has to perform a task at some point. You can consider this a callback pattern for example. My task is defined as a function. The task must have some context to operate on/with. My task does not return a value, but it may store results in the context.

I try to make this generic so it does not matter what kind of task this is and what kind of context it requires.

I made some code (which I tested to compile and run):

/// ImportantWork.h
#include <functional>

// Define the task signature.
typedef std::function<void(void *)> Task;

class ImportantWork {
public:
    // A function taking a long time, which will be calling the task when the time has come.
    void PerfomWorkTakingLongTime(Task task, void *taskContex);
};
/// ImportantWork.cpp
#include "ImportantWork.h"

void ImportantWork::PerfomWorkTakingLongTime(Task task, void *taskContex) {
    // Do a lot of work.

    // Now it is time to call the task.
    task(taskContex);

    // Perhaps do some more work.
}

Now it happens that my task is a class member function. In general it is not necessarily that the task will always be a class member function, it could also be a function not in a class. But my 'problem' occurs when it is a class member function.

/// MyClass.h
#include "ImportantWork.h"

class MyClass {
public:
    void StartWork();

private:
    void TaskCallback(void *context);
    int TaskVariable = 0;
    ImportantWork importantWork;
};
/// MyClass.cpp
#include "MyClass.h"

void MyClass::StartWork() {
    // Start work taking a long time and provide the TaksCallback as task and a reference to the MyClass instance as context.
    importantWork.PerfomWorkTakingLongTime(
        [&](void *context) { TaskCallback(context); },  // -- My question is about this line. -- //
        this);
}

void MyClass::TaskCallback(void *context) {
    // Manipulate a MyClass instance member.
    TaskVariable += 1;  // -- My question is about this line also. -- //

    // This does also work. Somehow, I expected this to be the only way.
    MyClass *pContext = (MyClass *)context;
    pContext->TaskVariable += 1;
}
/// Main.cpp
#include "MyClass.h"

int main(void) {
    MyClass myInstance;
    myInstance.StartWork();
    
    return 0;
}

This seems to work, however, I'm puzzled by my lambda expression (see MyClass.cpp): [&](void *context) { TaskCallback(context); }. [&] is capturing my class (this) implicitly as context. However, one line below, I provide the context explicitly as this.

I understand that a class member function needs a reference to its class instance. It seems that the compiler is implicitly providing this to the function as a sort of 'under the hood' function argument. In case of the lambda expression, [&] seems to takes care of this. However, it feels like I'm doubling things, providing the context twice.

Somehow I feel that I need a lambda expression where I don't want to capture any context by reference [&] or copy [=], just capturing nothing but it seems not to be possible.

Is there a good way to achieve what I'm trying.

Thank you.

EDIT: I can make my class member function static that way it has to use the explicit context provided. Still, the lambda expression is capturing context because of the [&]. Can this be avoided? Can I avoid using a lambda expression at all?


Solution

  • Somehow I feel that I need a lambda expression where I don't want to capture any context by reference [&] or copy [=], just capturing nothing but it seems not to be possible. Is there a good way to achieve what I'm trying.

    You can capture nothing, but then there's no this on which to call TaskCallback, so you have to get it from context.

    importantWork.PerfomWorkTakingLongTime(
        [](void *context) { static_cast<MyClass*>(context)->TaskCallback(nullptr); },
        this);
    

    Alternatively, you can capture this, so you can ignore context.

    importantWork.PerfomWorkTakingLongTime(
        [this](void *) { TaskCallback(nullptr); },
        nullptr);
    

    What you are doing is both, so you have redundancy in accessing the instance of your class.

    Still, the lambda expression is capturing context because of the [&].

    No it isn't. context is the parameter of the lambda, it only captures things accessible from the scope it is defined in, that it uses.

    N.b. in both of these I'm passing nullptr to TaskCallback, as that only needs the members of MyClass. It would be better to remove that parameter.