118

In the following code, the first call to foo is ambiguous, and therefore fails to compile.

The second, with the added + before the lambda, resolves to the function pointer overload.

#include <functional>

void foo(std::function<void()> f) { f(); }
void foo(void (*f)()) { f(); }

int main ()
{
    foo(  [](){} ); // ambiguous
    foo( +[](){} ); // not ambiguous (calls the function pointer overload)
}

What is the + notation doing here?

user3840170
  • 26,597
  • 4
  • 30
  • 62
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213

1 Answers1

121

The + in the expression +[](){} is the unary + operator. It is defined as follows in [expr.unary.op]/7:

The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument.

The lambda is not of arithmetic type etc., but it can be converted:

[expr.prim.lambda]/3

The type of the lambda-expression [...] is a unique, unnamed non-union class type — called the closure type — whose properties are described below.

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

Therefore, the unary + forces the conversion to the function pointer type, which is for this lambda void (*)(). Therefore, the type of the expression +[](){} is this function pointer type void (*)().

The second overload void foo(void (*f)()) becomes an Exact Match in the ranking for overload resolution and is therefore chosen unambiguously (as the first overload is NOT an Exact Match).


The lambda [](){} can be converted to std::function<void()> via the non-explicit template ctor of std::function, which takes any type that fulfils the Callable and CopyConstructible requirements.

The lambda can also be converted to void (*)() via the conversion function of the closure type (see above).

Both are user-defined conversion sequences, and of the same rank. That's why overload resolution fails in the first example due to ambiguity.


According to Cassio Neri, backed up by an argument by Daniel Krügler, this unary + trick should be specified behaviour, i.e. you can rely on it (see discussion in the comments).

Still, I'd recommend using an explicit cast to the function pointer type if you want to avoid the ambiguity: you don't need to ask on SO what is does and why it works ;)

dyp
  • 38,334
  • 13
  • 112
  • 177
  • Does that mean I can convert any member function to a function, or it only works on lambdas? – Fred Jul 23 '13 at 23:15
  • 3
    @Fred AFAIK member function pointers cannot be converted to non-member function pointers, let alone function lvalues. You can bind a member function via `std::bind` to a `std::function` object which can be called similarly to a function lvalue. – dyp Jul 23 '13 at 23:30
  • 2
    @DyP: I believe we can rely on the tricky. Indeed, suppose that an implementation adds `operator +()` to a stateless closure type. Assume that this operator returns something other than the pointer to function that the closure type converts to. Then, this would alter the observable behavior of a program which violates 5.1.2/3. Please, let me know if you agree with this reasoning. – Cassio Neri Jul 24 '13 at 15:20
  • 2
    @CassioNeri Yes, that's the point where I'm not sure. I agree the observable behaviour could change when adding an `operator +`, but this is comparing to the situation that there is no `operator +` to begin with. But it isn't specified that the closure type shall not have an `operator +` overload. "An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by [...]" but IMO *adding* an operator does not change the closure type to something different from what is "described below". – dyp Jul 24 '13 at 15:30
  • 3
    @DyP: The situation where there's no `operator +()` is exactly the one described by the standard. The standard allows an implementation to do something different from what is specified. For instance, adding `operator +()`. However, if this difference is observable by a program, then it's illegal. Once I asked in comp.lang.c++.moderated if a closure type could add a typedef for `result_type` and the other `typedefs` required to make them adaptable (for instance by `std::not1`). I was told that it could not because this was observable. I'll try to find the link. – Cassio Neri Jul 24 '13 at 16:22
  • 2
    @DyP: [Here](https://groups.google.com/forum/?fromgroups#!searchin/comp.lang.c$2B$2B.moderated/adaptable$20closure$20type$20daniel$20cassio/comp.lang.c++.moderated/6GM2DjHRMMc/xs5kb1nuSR4J) is the link. – Cassio Neri Jul 24 '13 at 16:25
  • @CassioNeri I agree this is a valid interpretation. Don't know exactly who "Daniel Krügler" is, but he seems to have done some proposals and worked on the Standard. Guess that settles it ;) – dyp Jul 24 '13 at 16:42
  • 6
    VS15 gives you this fun error: test.cpp(543): error C2593: 'operator +' is ambiguous t\test.cpp(543): note: could be 'built-in C++ operator+(void (__cdecl *)(void))' t\test.cpp(543): note: or 'built-in C++ operator+(void (__stdcall *)(void))' t\test.cpp(543): note: or 'built-in C++ operator+(void (__fastcall *)(void))' t\test.cpp(543): note: or 'built-in C++ operator+(void (__vectorcall *)(void))' t\test.cpp(543): note: while trying to match the argument list '(wmain::) test.cpp(543): error C2088: '+': illegal for class – Ed Lambert Jul 27 '16 at 22:58
  • 1
    @EdLambert Yes, msvc adds additional conversion functions to deal with different calling conventions of the target function (pointer) type... not sure if that's entirely conforming to the C++ Standard though. – dyp Jul 28 '16 at 12:30