By Daniel Frey


2013-09-19 07:51:31 8 Comments

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?

1 comments

@Daniel Frey 2013-09-19 07:51:31

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.

@Tadeusz Kopec 2013-09-19 08:14:17

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 2013-09-19 08:21:50

@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 2013-09-19 09:17:44

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 2013-09-19 10:04:59

@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 2013-09-19 10:11:23

(For non-primitive types, it's more customary to use a generic function called val() than an operator+ overload, though.)

@Andrew Tomazos 2013-09-19 11:04:26

@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 2013-09-19 15:13:28

@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 2013-09-23 16:38:51

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 2017-05-17 18:50:58

What is a "conversion-to-any-pointer" ?

@Daniel Frey 2017-05-17 20:16:38

@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 2017-05-17 20:38:45

But for the implicit operator+ not any pointer will do, it needs a pointer to <closure type>, unless I missed something.

@Daniel Frey 2017-05-18 15:00:06

@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 2017-05-18 21:30:43

@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.

Related Questions

Sponsored Content

21 Answered Questions

[SOLVED] What is the "-->" operator in C++?

11 Answered Questions

[SOLVED] What is move semantics?

13 Answered Questions

[SOLVED] What is a smart pointer and when should I use one?

7 Answered Questions

11 Answered Questions

[SOLVED] What are rvalues, lvalues, xvalues, glvalues, and prvalues?

35 Answered Questions

8 Answered Questions

[SOLVED] What is The Rule of Three?

8 Answered Questions

[SOLVED] What is a lambda expression in C++11?

11 Answered Questions

[SOLVED] What does the explicit keyword mean?

Sponsored Content