4

I know this could seem very stupid for non-noob C++ developers, but what are the differences between these 4 lambda expressions? Code:

#include <iostream>
#include <math.h>
#include <functional>

inline double MyFunction(double a, double b, double c) {
    return (a + b + c);
}

inline void FunctionWrapper(std::function<double(double)> tempFunct, double value) {
    std::function<double(double)> funct;

    funct = tempFunct;

    std::cout << "result: " << funct(value) << std::endl;
}

int main()
{    
    double value = 100.0;

    FunctionWrapper([](double value) { return MyFunction(value, 1.0, 2.0); }, value);
    FunctionWrapper([](double value) -> double { return MyFunction(value, 1.0, 2.0); }, value);

    FunctionWrapper([value](double value) { return MyFunction(value, 1.0, 2.0); }, value);
    FunctionWrapper([value](double value) -> double { return MyFunction(value, 1.0, 2.0); }, value);
}

It seems it does the same? Either using two different "notations" and using the value as closure?

markzzz
  • 47,390
  • 120
  • 299
  • 507
  • 3
    btw stricly speaking all lambda expression are different from each other. Even `auto a = [](){};` is not the same as `auto b = [](){};` as they are of different type – 463035818_is_not_an_ai Oct 25 '18 at 09:49
  • The third and forth variant is a case of variable shadowing: https://godbolt.org/z/GlUZbu. That's why it's good to enable all warnings and warnings as errors. – Trass3r Oct 25 '18 at 10:33
  • @Trass3r: they are actually ill-formed according to C++17: https://stackoverflow.com/questions/52947934/no-compiler-diagnostic-when-identifier-in-a-simple-capture-appears-as-the-declar – Andriy Tylychko Oct 29 '18 at 15:25
  • I know. It's still shadowing. And rejected now in clang as well. – Trass3r Oct 29 '18 at 22:43

4 Answers4

7

In this context, they all produce the same results. However, there are logical differences between them.

  • [](double value) { return MyFunction(value, 1.0, 2.0); }

    This is a lambda which takes a single parameter of type value and passes this into MyFunction. Its return type is deduced from the return statement to be that of MyFunction, which is double.

  • [](double value) -> double { return MyFunction(value, 1.0, 2.0); }

    This is the same lambda as before, but this time its return type is explicitly specified to be double. It's the same in this case, but it would be different from the first one if the return type of MyFunction was something else. In that case, the first one would return what MyFunction returns, while this one would still return double.

  • [value](double value) { return MyFunction(value, 1.0, 2.0); }

    This one depends on the standard version used. In C++11 and 14, this one captures main's local variable value. However, that capture is hidden by the lambda's parameter value, so it's effectively useless. It would be different if the lambda had been declared as e.g. [value](double v) { return MyFunction(value, 1.0, 2.0); }. This would have passed on the captured value, and not its parameter.

    In C++17 and above, this was changed and it's actually ill-formed (compilation error). Naming a lambda parameter the same as something you capture is no longer allowed.

    Since the change was a defect report (CWG 2211), it applies retroactively and so it's legal for compilers to reject such code even in earlier C++ versions.

  • [value](double value) -> double { return MyFunction(value, 1.0, 2.0); }

    Same as lambda number 3, with explicit return type specification (the difference between 3 and 4 is exactly the same as between 1 and 2).

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Seems like gcc still accept the ill-formed lambda. But then I've seen gcc being far laxer than VS with the standard. – Matthieu Brucher Oct 25 '18 at 10:03
  • 1
    Well the change is made by [CWG 2211](https://wg21.link/cwg2211), which is a DR, which means it also applies to older standards. (But compilers may need time to catch up.) – cpplearner Oct 25 '18 at 10:03
  • @Angew: so writing `[value](double v) { return MyFunction(value, 1.0, 2.0); }` will be converted in somethings like `{ double value = externalvalue; return MyFunction(value, 1.0, 2.0); }`? – markzzz Oct 25 '18 at 10:11
  • 1
    @markzzz Pretty much. More specifically, just like any other capture, `value` will become a member of the closure type, initialised with the captured variable `value`. – Angew is no longer proud of SO Oct 25 '18 at 10:13
  • and why if I'm within a Class and I want to pass `myClass.value`, I need to pass `this` instead? – markzzz Oct 25 '18 at 10:17
  • 1
    @markzzz This is drfiting towards a separate question, which should be treated with its own research (suggested starting point: [cppreference on lambdas](https://en.cppreference.com/w/cpp/language/lambda)) and if that proves insufficient, a new SO question. – Angew is no longer proud of SO Oct 25 '18 at 10:19
  • It's fixed on gcc trunk. – Trass3r Oct 25 '18 at 10:26
5

The second lambda differs from the first in that you've specified the return type explicitly. Since the deduced return type of the first lambda is the same, there is no difference.

The third and fourth lambdas are ill-formed, since they declare a parameter with same name as a capture. See the standard rule:

[expr.prim.lambda.capture]

If an identifier in a simple-capture appears as the declarator-id of a parameter of the lambda-declarator's parameter-declaration-clause, the program is ill-formed. [ Example:

void f() {
  int x = 0;
  auto g = [x](int x) { return 0; }    // error: parameter and simple-capture have the same name
}

— end example  ]

This wording was adopted in C++17.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
3

The first lambda is the usual one.

The second lambda indicates that its return type is double.

The last two lambdas are not sound. value is both captured by value and its parameter, that shouldn't be tried (there is a warning indicating that the capture is not working). Either it's a parameter, or a captured variable (by value or ref).

I suppose, they should be something like:

[foo](double value) { return MyFunction(value, 1.0, foo); }, value);
Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
1

The difference between your first and second lambda and between the third and forth one is that you either do or do not explicitly specify the return type. Here it results in identical functions.

The difference between your first two lambdas and lambdas three and four is capturing. But your examples are not suited for illustrating the effects of capturing. Look at the following code (live demo).

int main()
{    
    double v = 100.0;

    auto lam1= [](double val) { return MyFunction(val, 1.0, 2.0); }; //A
    auto lam2= [v](double val) { return MyFunction(v, 1.0, 2.0); }; //B
    auto lam3= [&v](double val) { return MyFunction(v, 1.0, 2.0); }; //C

    FunctionWrapper( lam1, v++ ); //1
    FunctionWrapper( lam2, v++ ); //2
    FunctionWrapper( lam3, v++ ); //3
    std::cout << "v: " << v << '\n';
}

While lam1 does not capture anything, lam2 captures v by value and lam3 captures v by reference. Output of the three FunctionWrapper calls is 103, 103, 106, resp. This is because although v got changed in line //1 it's old value is used in line //2. That is, capturing by value means the value v holds at the time lam2 is initialized in line //B is stored in lam2. In line //3 on the other hand the current value of v is used. This is because lam3 holds a reference to v.

Claas Bontus
  • 1,628
  • 1
  • 15
  • 29