Resolving Ambiguous Overload on Function Pointer and Std::Function For a Lambda Using + (Unary Plus)

Resolving ambiguous overload on function pointer and std::function for a lambda using + (unary plus)

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 ;)

Ambiguous overload on function pointer and std::function

Non-capturing lambdas have an implicit conversion to a function pointer with the same signature. This is specified in chapter 5.1.2 paragraph 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.

Why would one want to put a unary plus (+) operator in front of a C++ lambda?

It's not a feature of lambda and more is a feature of implicit type conversion.

What happens there is stemming from the fact that a captureless lambda can be implicitly converted to a pointer to function with same signature as lambda's operator(). +[]{} is an expression where unary + is a no-op , so the only legal result of expression is a pointer to function.

In result auto funcPtr would be a pointer to a function, not an instance of an object with anonymous type returned by lambda expression. Not much of advantage in provided code, but it can be important in type-agnostic code, e.g. where some kind of decltype expression is used. E.g.

#include <type_traits>

void foo(int);

template<class T>
struct is_foo : std::is_same<T, decltype(&foo)> {};

int main()
{
auto foo1 = +[](int)->void {};
auto foo2 = [](int)->void {};
static_assert(is_foo<decltype(foo1)>::value, "foo1 is not like foo");
static_assert(is_foo<decltype(+foo2)>::value, "+foo2 is not like foo");
static_assert(is_foo<decltype(foo2)>::value, "foo2 is not like foo");
}

Note that you can do same with foo: std::is_same<T, decltype(+foo)> {};

Albeit some platforms may not support that, because they inherently may have a variety of function pointers with different calling convention and the expression will be ambiguous.

What does + mean in cpp lambda declaration auto fun1 = +[](){};

A lambda has a compiler-generated type. If you just assign the lambda as-is to an auto variable, then the auto variable's type will be deduced as the lambda's generated type.

A non-capturing lambda is implicitly convertible to a plain function pointer. By placing + in front of the lambda, the lambda is explicitly converted to a function pointer, and then the auto variable's type will be deduced as the function pointer type, not the lambda type.

A capturing lambda cannot be converted to a function pointer at all, which is why you get the compiler error.

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

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.

Testing static replacement for std::function for non stateless lambda support

ETL documents that etl::delegate doesn't own the lambda at all, see https://www.etlcpp.com/delegate.html. It only stores a pointer to the passed object. It doesn't store the lambda at all. See also the code at https://github.com/ETLCPP/etl/blob/master/include/etl/private/delegate_cpp11.h#L117.

Contrary to what the linked documentation seems to imply, it is always undefined behavior to pass the constructor of etl::delegate a lambda expression directly as argument, no matter whether it is stateless or not. Because etl::delegate stores a pointer to the passed object, when operator() is called it will try to call a non-static member function on an out-of-lifetime object.

The undefined behavior is just less likely to cause unintended behavior of the compiled program if the lambda is stateless and so the compiled member function called on the invalid pointer doesn't actually have to access/dereference the pointer.

You must store the lambda somewhere outside the etl::delegate.

I am not sure why the author didn't add a lambda-to-function-pointer conversion for non-capturing lambdas and/or disallowed rvalue arguments to the constructor. The way it is written now it is extremely easy to misuse.


vl::Func seems to implement a pattern similar to std::function and does own the passed callable. That also means it uses new to store the callable in dynamic memory, but no matching delete as far as I can tell.

Therefore it is not a static-allocation replacement. It does use dynamic allocation.

(Sorry for my previous assertion that it is leaking the object. I completely misread the code. I don't see any defect in the implementation anymore as far as the superficial look I had at the code.)


It is not possible to implement what std::function does in generality without dynamic allocation. A static allocation replacement for std::function which also owns the callable must have some parameter limiting the maximum size of a callable object it can store or something similar because the callable would need to be embedded directly into the storage of the std::function-replacement object.

From the examples above you can see that problem. Both of them don't have such a parameter. So one of the replacements isn't owning and the other isn't statically allocating, they can't be doing both.


To test whether the lambda is stateless you need to apply is_stateless to the type of the lambda expression, e.g.

auto lambda = [&args...](Func v, int& ret) { ret = v(args...); };

CHECK_EQUAL(true, is_stateless<decltype(lambda)>::value);

You are applying is_stateless to a type that is not a lambda at all.

Select unary vs. binary std::transform function overload automatically based on callable's signature

With C++17 you can mix if constexpr and std::is_invocable:

if constexpr (std::is_invocable_v<
decltype(operation), decltype(*src.begin())>) {
std::transform(src.begin(), src.end(), dst.begin(), operation);
}
else {
std::transform(src.begin(), src.end(), dst.begin(), dst.begin(), operation);
}

You could also check that operation is valid in the second case but that would require an extra else branch to avoid silencing compile-time errors when (neither branch is valid), and this would require some always_false shenanigan.



Related Topics



Leave a reply



Submit