5

Google is showing me lots of SO posts on passing lambdas as arguments, but they all seem to be C++0x/C++11 based (e.g. Use a lambda as a parameter for a C++ function) and I understand this is an area that has improved in more recent C++ versions?

I want to pass what is effectively a very simple delegate return MyGlobals.GetX() as a method variable - in my case MyGlobals is a global object. i.e. in pseudocode

//stores and/or calls the lambda
myObject.SetXGetter({return MyGlobals.GetX()});

MyObject::SetXGetter(lambda?)
{
 this->mLambda = Lambda;
 cout << mLambda();
}

Older C++ versions made this quite messy having to use STL wrappers and temporary types, so in C++17 and above, how neatly can it be done... what would SetGetter look like and can it be called passing the lambda directly in?

(If there are different alternatives for even newer versions, please share)

Mr. Boy
  • 60,845
  • 93
  • 320
  • 589
  • 3
    Isn't it just `myObject.SetXGetter( []{ return MyGlobals.GetX(); } );`? What did you find? What did you try? What about it didn't you like? Where did you read that versions after C++11 improve this, and what is it that needs improved anyway? It's an unclear question. – underscore_d Feb 16 '21 at 12:42
  • @underscore_d I don't know, I haven't used lambdas hardly at all. Given I have provided an exact example, it seems quite clear... how can I most simply declare and call `SetXGetter`? As I said, all I find are answers about older C++ where you use `functors` and so on – Mr. Boy Feb 16 '21 at 12:45
  • 1
    Worked for me. Are you trying to use a lambda directly or as `std::function`? – Eljay Feb 16 '21 at 12:46
  • @Eljay what worked for you, nobody posted valid code? I don't know what your question means, but I want the closest to my pseudo code – Mr. Boy Feb 16 '21 at 12:49
  • 1
    maybe you are confusing things. It was never necessary to have messy wrappers and/or "temporary types" to pass a lambda to a function. Btw the answers to the question you link still apply – 463035818_is_not_an_ai Feb 16 '21 at 12:59

3 Answers3

8

A function that just wants to invoke a passed lambda or other functor during its own execution can be a function template, deducing the type of the functor:

template <typename F>
void use_it(F&& func) {
    do_something_with(func());
}

Here you want to store the functor to be invoked later, so we'll need a common type that can wrap around various sorts of functors. This is what std::function is for. Assuming a return type of int:

#include <functional>

class MyObject {
// ...
private:
    std::function<int()> mLambda;
};
void MyObject::SetXGetter(std::function<int()> func) {
    mLambda = std::move(func);
}

Your call is missing the initial [captures] which is required for a lambda expression, even if nothing is captured:

myObject.SetXGetter([]{ return MyGlobals.GetX(); });
aschepler
  • 70,891
  • 9
  • 107
  • 161
3

and I understand this is an area that has improved in more recent C++ versions?

Not in general. It has been expanded to some more complex cases, but the simple cases are pretty much identical to C++11.

Simplest way to pass a lambda as a method parameter in C++17

The options remain the same:

  • You can have a template and pass the lambda as-is.
  • Or you can wrap the lambda in a type erasing wrapper such as std::function in which case you can pass it into a non-template function.
  • Or if the lambda is non-capturing, then it can be converted to a function pointer which can also be passed into a non-template.

Which one is simplest can vary on context and personal opinion. Function wrapper works the same in widest number of different cases, to that's a simple choice. Function pointer doesn't involve dynamic allocation and is simplest to understand at a low level. Template doesn't involve any sort of wrapping or conversion, but it is a template.


this->mLambda = Lambda;

Given that you wish to store the callable as a modifiable member, storing it as a lambda is not an option. You're left with function wrapper and function pointer. You can still use a template for the function, but the lambda must be immediately wrapped or converted anyway, so there isn't much to be gained by using a template.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • I don't understand 3 enumerated options and the penultimate sentence. It seems that you could have a template method that takes the lambda as `F&&`, yet store the argument in a `std::function`. To which choice do these options apply? – MSalters Feb 16 '21 at 13:14
  • @MSalters I Reworder for clarity. – eerorika Feb 16 '21 at 13:22
1

For C++20 and later:

If you don't intend to store the passed function you can do the following

void use_it(auto func) {
    func();
}

You can use concepts perhaps to constrain it even more so that it does not except anything but at least a callable.

If you want to store the argument you will unfortunately still need to name it like the other answers have suggested, and it must be able to convert to the type you specified:

#include <functional>

class MyObject {
public:
    void SetXGetter(auto func) {
        mLambda = std::move(func);
    }

private:
    std::function<int()> mLambda;
};
CJCombrink
  • 3,738
  • 1
  • 22
  • 38
  • I know the question is about C++17 but I was curios myself on how C++20 improves on this and decided to post my findings as an answer. For the code on compiler explorer: https://godbolt.org/z/r7qz13 – CJCombrink Feb 16 '21 at 14:16
  • 1
    A problem with `(auto func)` is that passing a functor lvalue could make an unnecessary copy of the original argument type. `use_it` could just do `auto&& func` to deal with `const` lvalue and other cases separately, but ignore the value category. But `SetXGetter` should probably use perfect forwarding, which is difficult with `auto` type. Or stick with `SetXGetter(std::function)`, which puts any "not convertible" errors closer to the user's context, and automatically deals with overloading/SFINAE better. – aschepler Feb 16 '21 at 15:07
  • @CJCombrink very interesting thanks for posting – Mr. Boy Feb 16 '21 at 15:44