27

I wanted to create a lambda in the following way:

auto l1 = condition ?
          [](){ return true; } :
          [number](){ return number == 123; };

However, I got error:

operands to ?: have different types ‘main()::<lambda()>’ and ‘main()::<lambda()>’

Obviously, the types seem to be the same. I thought, that capturing number in only one of lambdas might be a problem, but I get the same error for these:

//check if capturing number in both lambdas would help
auto l2 = condition ?
          [number](){ return true; } :
          [number](){ return number == 123; };
//maybe the first lambda capture was optimised out? let's make sure:
auto l3 = condition ?
          [number](){ return number != 123; } :
          [number](){ return number == 123; };

I'm aware I can do it other way (below), but I'm wondering what is the cause of this behavior. It was compiled with GCC6.3.1, C++14 enabled.

//compiles      
auto l4 = condition ?
          [](const int){ return true; } :
          [](const int number){ return number == 123; };
knopers8
  • 438
  • 5
  • 11

3 Answers3

35

Every lambda expression has unique type (i.e. the closure type, which is a unique unnamed non-union non-aggregate class type), even with the same signature and function body; the compiler just can't deduce the common type of ternary conditional operator for the variable declared by auto, the two closure types are irrelevant at all.

You can use std::function instead. e.g.

std::function<bool()> l1;
if (condition)
     l1 = [](){ return true; };
else
     l1 = [number](){ return number == 123; };

For l4, note that the lambda-expression with empty capture list could be converted to the corresponding function pointer implicitly. In this case both of them could be converted to the same function pointer type (i.e. bool(*)(int)), which then could be deduced as the common type of ternary conditional operator and the type of l4.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 2
    Then why l4 does compile? – knopers8 Jan 23 '18 at 08:42
  • 14
    @knopers8 because non-capturing lambdas implicitly convert to their common function pointer type (eg bool(*)()) ... – Massimiliano Janes Jan 23 '18 at 08:44
  • Because they have the same signature. Just because the paramameter in first option isn't named it doesn't mean it's different. – Maroš Beťko Jan 23 '18 at 08:44
  • 2
    @MarošBeťko 2 lambdas with same signatures even 2 lambdas with identical bodies have different types. It works for the reason specified by Massimiliano – bolov Jan 23 '18 at 08:49
  • 2
    @knopers8 The comments from all are correct. Explanations added to the answer too. – songyuanyao Jan 23 '18 at 08:55
  • Should I expect, that this 'problem' will be somehow handled in future c++ standards? – knopers8 Jan 23 '18 at 09:01
  • Why every lambda expression have different type even when argument types and return type are same – PapaDiHatti Jan 23 '18 at 09:02
  • @knopers8 I don't think so. Because that needs the compiler to check the implmentation details of the lambdas, and the context where they're declared are identical or not. – songyuanyao Jan 23 '18 at 09:04
  • @Kapil The compiler doesn't check the implementation details and the contexts of the lambda, just generate a new closure type for every lambda expression. – songyuanyao Jan 23 '18 at 09:06
  • @Kapil A lambda is an object that may store captured variables. In theory, two lambda objects that capture the same things in the same way could have the same type, but this is a special case that's not very useful, and very difficult to check for (maybe even impossible in the general case). – molbdnilo Jan 23 '18 at 10:26
  • @knopers8 it isn't a problem. Much of the time you don't care about the exact type, just that it satisfies the "interface" you need. Only when you do need to have a *specific* type do you need to use something like `std::function`. You **can** have multiple *instances* of the same closure type, by having code evaluate the *same lambda expression* multiple times (say, in a loop) – Caleth Jan 25 '18 at 12:12
  • @molbdnilo I can imagine a compiler( / linker) noticing that it's output for two expressions are identical and de-duplicating them, but that's after types stop mattering – Caleth Jan 25 '18 at 12:14
  • @Caleth That's basically why I put 'problem' in '', it is not a big deal. But still, I perceived `l1` to be the most clean way to accomplish my goal, also with less performance overhead than `std::function` (the real, not simplified `l1` would be used inside 4x nested loop). – knopers8 Jan 25 '18 at 13:21
  • I'd suggest `auto l5 = [=](){ return condition ? true : number == 123; }`? – Caleth Jan 25 '18 at 13:42
  • Then `condition` would be checked each time `l5` is used, which is not necessary - it is determined each time before entering 3x nested loop. Anyway, there is no need to debate too long on this little matter. Still, thanks for help :) – knopers8 Jan 25 '18 at 14:10
16

If you really want to use the conditional operator you can do it like this:

auto l1 = condition
          ? std::function<bool()>{[](){ return true; }}
          : std::function<bool()>{[number](){ return number == 123; }};

In C++17 this can be simplified thanks to Class template argument deduction:

auto l1 = condition
          ? std::function{[](){ return true; }}
          : std::function{[number](){ return number == 123; }};
bolov
  • 72,283
  • 15
  • 145
  • 224
4

A lambda in C++ is an instance of local class implementing functor contract. I mean, operator() etc. And these classes are unrelated and have different types.

Your code is equivalent to

struct l1 {
  bool operator () const {
    return true;
  }
};

struct l2 {
  private int number_:
  l2(int number): number_(number){}

  bool operator () const {
    return number_ == 123;
  }
};

int number = ???
auto l3 = condition ? l1 : l2(number);
Minor Threat
  • 2,025
  • 1
  • 18
  • 32