290

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?

Community
  • 1
  • 1
Daniel Frey
  • 52,317
  • 11
  • 110
  • 168
  • 1
    It is interesting that for a capturing lambda it would not work. – Matthieu M. Sep 19 '13 at 07:56
  • 4
    @MatthieuM. Because capturing lambdas don't decay to function pointers! `;)` – Mark Garcia Sep 19 '13 at 07:57
  • 1
    @MarkGarcia: exactly, that may be the only difference between capturing and non-capturing, and thus a hint as to the solution. – Matthieu M. Sep 19 '13 at 07:58
  • 4
    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 `+`). – Cassio Neri Sep 19 '13 at 09:02
  • 4
    At least related: [Resolving ambiguous overload on function pointer and std::function for a lambda using +](http://stackoverflow.com/q/17822131/420683) – dyp Sep 19 '13 at 09:02
  • 2
    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 = +*??(:>();` – dyp Sep 19 '13 at 09:31
  • @DyP Ouch! Nice touch with the alternative tokens and trigraphs, looks like your next entry for the IOC++CC... `:)` – Daniel Frey Sep 19 '13 at 09:36
  • 1
    @MatthieuM. just for reference, two lambdas with the same signature have different types. using the `std::function` template avoids wraps the type difference. – Alexander Oh Sep 19 '13 at 11:43

1 Answers1

265

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.

Community
  • 1
  • 1
Daniel Frey
  • 52,317
  • 11
  • 110
  • 168
  • 19
    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. – Tadeusz Kopec Sep 19 '13 at 08:14
  • @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 `-`. – Daniel Frey Sep 19 '13 at 08:21
  • 20
    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. – Johannes Schaub - litb Sep 19 '13 at 09:17
  • 6
    @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 Sep 19 '13 at 10:04
  • (For non-primitive types, it's more customary to use a generic function called `val()` than an `operator+` overload, though.) – Potatoswatter Sep 19 '13 at 10:11
  • 1
    @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(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? – Andrew Tomazos Sep 19 '13 at 11:04
  • 1
    @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. – Potatoswatter Sep 19 '13 at 15:13
  • 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. – Ben Voigt Sep 23 '13 at 16:38
  • What is a "conversion-to-any-pointer" ? – ThomasMcLeod May 17 '17 at 18:50
  • @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. – Daniel Frey May 17 '17 at 20:16
  • But for the implicit `operator+` not any pointer will do, it needs a pointer to , unless I missed something. – ThomasMcLeod May 17 '17 at 20:38
  • @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 [...]". – Daniel Frey May 18 '17 at 15:00
  • @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. – ThomasMcLeod May 18 '17 at 21:30