2

I would like to use lambdas to conditionally expand the functionality of a function within a class. There is no problem doing this outside of the class scope (see example), but the minimal working example below leads to a segmentation fault when calling a function that has modified itself within the class. Can anyone explain why this code fails and how I should be thinking about lambdas within a class differently than lambdas outside of a class?

#include <functional>
#include <iostream>

class MyClass 
{
  public:
    MyClass(bool modify);
    int a;
    std::function<void (void)> myFunc;
};

MyClass::MyClass(bool modify) 
{
    a = 2;
    myFunc = [this](){ std::cout << "1. Inside my initialized function; a=" 
                                 << this->a << std::endl;};
    //myFunc(); -- works with or without being commented
    if (modify)
    {
        myFunc = [this](){ this->myFunc(); 
                           std::cout << "2. adding an output line to my "
                                     << "initialized function" << std::endl;};
        //EDIT: Originally tried
        //       myFunc = [myFunc](){ myFunc(); std::cout << endl; };
        //       but this will not compile.  See edit of post below
        //myFunc(); -- fails with or without being commented
    }
}

int main(int argc, char **argv) 
{
    std::function<void (void)> func;
    int a = 2;
    func = [a](){ std::cout << "1. Inside my initialized function; a=" 
                            << a << std::endl;};
    func = [func](){ func(); 
                     std::cout << "2. adding an output line to my initialized "
                               << "function" << std::endl;};
    std::cout << "Result of modified function outside of class: " << std::endl;
    func();
    std::cout << std::endl;

    std::cout << "Result of unmodified function in class: " << std::endl;
    MyClass myClassNoMod(false);
    myClassNoMod.myFunc();
    std::cout << std::endl;

    std::cout << "Result of modified function in class: " << std::endl;
    MyClass myClassMod(true);
    myClassMod.myFunc();

    return 0;
}

Edit PaulR gave the reasonable suggestion of capturing myFunc rather than this in the update of myFunc. In my original implementation this is what I tried:

myFunc = [myFunc](){myFunc(); std::out << "stuff\n"; };

but this lead to the compiler errors

error: 'myFunc' in capture list does not name a variable
        myFunc = [myFunc](){ myFunc();
                  ^
error: 'this' cannot be implicitly captured in this context
        myFunc = [myFunc](){ myFunc();
  • Why are you reusing variables and variable-names in such a bad way? – Some programmer dude Apr 04 '18 at 09:42
  • Can you educate rather than disparage me? – user2712187 Apr 04 '18 at 09:47
  • In your `main` function you have `func`. One moment if means the simple lambda where you print the first message. The next moment you reuse it for the second lambda which seems to capture itself and call itself recursively. That's bad code that is hard to follow and maintain. It's worse in the class where `myFunc` *do* call itself recursively without stopping condition. – Some programmer dude Apr 04 '18 at 10:05
  • Thank you for letting me know what you meant by bad. I can sympathize with your view. My hope/idea was to make the function abstract in a similar way that variables are abstract (e.g. there is no issue with using the value of a variable to update itself, so can I use the existing behavior of a function to modify that functions behavior?) – user2712187 Apr 04 '18 at 10:20

1 Answers1

4

In your class you capture the this pointer and not the previous value of myFunc, thus your lambda will recursively call itself forever, since at call time the myFunc member will be already changed to the new lambda.

In main you capture the previous value of func by value and thus it does what you expect.

So I would suggest capturing a copy of myFunc by value (i.e. without &) instead of this.

if (modify)
{
    auto previousFunc = std::move(myFunc);
    myFunc = [previousFunc](){ previousFunc(); 
                       std::cout << "2. adding an output line to my "
                                 << "initialized function" << std::endl;};
}

If you are using C++14 or newer you can also use lambda capture expressions to avoid making a copy and directly moving the previous function into the lambda capture:

if (modify)
{
    myFunc = [previousFunc{std::move(myFunc)}](){ previousFunc(); 
                       std::cout << "2. adding an output line to my "
                                 << "initialized function" << std::endl;};
}
PaulR
  • 3,587
  • 14
  • 24
  • I originally tried doing this, but the code would not compile. Replacing the modified update of `myFunc` with `myFunc = [myFunc](){ myFunc(); std::cout << "new out\n"; };` leads to the errors `error: 'myFunc' in capture list does not name a variable` along with `error: 'this' cannot be implicitly captured in this context` – user2712187 Apr 04 '18 at 12:43
  • Sorry, I did not know that you cannot capture class members directly. I suggest making a local copy of myFunc. – PaulR Apr 04 '18 at 13:11