A positive lambda: '+[]{}' - What sorcery is this?

33,490

Yes, the code is standard conforming. The + triggers a conversion to a plain old function pointer for the lambda.

What happens is this:

The compiler sees the first lambda ([]{}) and generates a closure object according to §5.1.2. As the lambda is a non-capturing lambda, the following applies:

5.1.2 Lambda expressions [expr.prim.lambda]

6 The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.

This is important as the unary operator + has a set of built-in overloads, specifically this one:

13.6 Built-in operators [over.built]

8 For every type T there exist candidate operator functions of the form

    T* operator+(T*);

And with this, it's quite clear what happens: When operator + is applied to the closure object, the set of overloaded built-in candidates contains a conversion-to-any-pointer and the closure type contains exactly one candidate: The conversion to the function pointer of the lambda.

The type of test in auto test = +[]{}; is therefore deduced to void(*)(). Now the second line is easy: For the second lambda/closure object, an assignment to the function pointer triggers the same conversion as in the first line. Even though the second lambda has a different closure type, the resulting function pointer is, of course, compatible and can be assigned.

Share:
33,490

Related videos on Youtube

Daniel Frey
Author by

Daniel Frey

Updated on September 24, 2020

Comments

  • Daniel Frey
    Daniel Frey over 3 years

    In Stack Overflow question Redefining lambdas not allowed in C++11, why?, a small program was given that does not compile:

    int main() {
        auto test = []{};
        test = []{};
    }
    

    The question was answered and all seemed fine. Then came Johannes Schaub and made an interesting observation:

    If you put a + before the first lambda, it magically starts to work.

    So I'm curious: Why does the following work?

    int main() {
        auto test = +[]{}; // Note the unary operator + before the lambda
        test = []{};
    }
    

    It compiles fine with both GCC 4.7+ and Clang 3.2+. Is the code standard conforming?

    • Matthieu M.
      Matthieu M. over 10 years
      It is interesting that for a capturing lambda it would not work.
    • Mark Garcia
      Mark Garcia over 10 years
      @MatthieuM. Because capturing lambdas don't decay to function pointers! ;)
    • Matthieu M.
      Matthieu M. over 10 years
      @MarkGarcia: exactly, that may be the only difference between capturing and non-capturing, and thus a hint as to the solution.
    • Cassio Neri
      Cassio Neri over 10 years
      Another + sourcery follows. Try this on GCC: struct foo { static const int n = 100; }; int main() { return std::max(0, +foo::n); }. If you remove the + it fails to link, which is standard conforming behavior. VS2010 has no trouble in linking it (even without the +).
    • dyp
      dyp over 10 years
    • dyp
      dyp over 10 years
      Let's add some more magic: auto test = *[]{}; (note x is still a function pointer here, I think due to decaying) and then.. auto test = +*[]{};. Of course you can repeat this infinitely: auto test = *+*+*+[]{};. And my favourite: auto test = +*??(:>()<%??>;
    • Daniel Frey
      Daniel Frey over 10 years
      @DyP Ouch! Nice touch with the alternative tokens and trigraphs, looks like your next entry for the IOC++CC... :)
    • Alexander Oh
      Alexander Oh over 10 years
      @MatthieuM. just for reference, two lambdas with the same signature have different types. using the std::function template avoids wraps the type difference.
  • Tadeusz Kopec for Ukraine
    Tadeusz Kopec for Ukraine over 10 years
    Fascinating. And what's the point of unary + for pointers? I understand it exists for numeric types for completeness with unary -. But unary - with pointers makes no sense.
  • Daniel Frey
    Daniel Frey over 10 years
    @TadeuszKopec That's a good question and I'm afraid I don't have an answer for that. Note that for pointers 13.6/8 only defines +, not -.
  • Johannes Schaub - litb
    Johannes Schaub - litb over 10 years
    It is useful if you want to force decay of arrays or functions and if you want to force promotion of small integer types or unscoped enumerations.
  • Potatoswatter
    Potatoswatter over 10 years
    @TadeuszKopec … And for all types, it removes lvalue-ness. You can use it to pass a value instead of a reference to a function overloaded on both, e.g. by perfect forwarding.
  • Potatoswatter
    Potatoswatter over 10 years
    (For non-primitive types, it's more customary to use a generic function called val() than an operator+ overload, though.)
  • Andrew Tomazos
    Andrew Tomazos over 10 years
    @Potatoswatter: "And for all types, it removes lvalue-ness." +lvalue is not defined for all types, and for some of those where it is it is just an overloaded function call so could produce any value category of any unrelated type, for some yet other cases it doesn't produce the same type. It would be an odd use to use it to pass by value instead of reference. The better way would be with static_cast<T>(lvalue) to get a prvalue of type T from an lvalue. "customary to use a generic function called val()": This isn't clear, can you show an example/link of such a val() function?
  • Potatoswatter
    Potatoswatter over 10 years
    @user1131467 I simply mean, if you aren't specifically using the built-in operator+, you should define a function val which effectively does the static_cast you mention, rather than sticking to prefix + notation.
  • Ben Voigt
    Ben Voigt over 10 years
    The advantage of Potatoswatter's val function is that it uses implicit conversions, not a forcible cast. So it cannot accidentally break if the type changes later.
  • ThomasMcLeod
    ThomasMcLeod about 7 years
    What is a "conversion-to-any-pointer" ?
  • Daniel Frey
    Daniel Frey about 7 years
    @ThomasMcLeod It's what I quoted from 13.6: operator + has candidates like T* operator+(T*) and therefore any pointer will do. The operator tries to find some way to apply to its argument, the lambda. The lambda can be converted to a pointer and it is the only candidate that is available, so it is converted to the pointer to the function, which return the pointer itself as specified by 13.6.
  • ThomasMcLeod
    ThomasMcLeod about 7 years
    But for the implicit operator+ not any pointer will do, it needs a pointer to <closure type>, unless I missed something.
  • Daniel Frey
    Daniel Frey about 7 years
    @ThomasMcLeod No, that's the whole point, any pointer conversion will do. That's what I mean by conversion-to-any-pointer. [build.over] does not form actual overloads like you'd normally write, it just tries to find anything that can provide a pointer (in this case). From the intro of [build.over]: "[...] operator overload resolution can resolve to a built-in operator only when an operand has a class type that has a user-defined conversion to a non-class type appropriate for the operator, or [...]".
  • ThomasMcLeod
    ThomasMcLeod about 7 years
    @DanielFrey, my apologies. I misunderstood the meaning of "built-in". I thought we were talking about member function unary overloaded operators as in 13.5.1.