I'm trying to create a std::vector
that can hold std::function
objects of different signatures using std::variant
.
Why does the following code not compile:
#include <functional>
#include <variant>
#include <vector>
int main()
{
std::vector<std::variant<
std::function< int (const std::vector<float>&, int) >,
std::function< float (const std::vector<float>&, int) >
>> func_vector;
func_vector.emplace_back( [] (const std::vector<float>& ret, int index) { return ret.size(); });
return 0;
}
The problem happens during the emplace_back()
. Compiling this gives a long list of errors, the first one listed being:
error: no matching function for call to ‘std::variant<std::function<int(const std::vector<float, std::allocator<float> >&, int)>, std::function<float(const std::vector<float, std::allocator<float> >&, int)> >::variant(main()::<lambda(const std::vector<float>&, int)>)’
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It says that it cannot find a matching function, but for what call exactly?
The lambda I'm trying to emplace has exactly the signature of one of the types I specified in the variant, so all should be fine, shouldn't it?
emplace_back
should forward the lambda directly to the variant initialization. And there is a converting constructor that can initialize a member of the variant from any argument convertible to the member type. The issue, however, is that both members of the variant can be initialized from this lambda, creating an ambiguity.
Yes, your lambda is a valid initializer for a std::function< float (const std::vector<float>&, int) >
. This is due to the way a std::function
performs type erasure. It casts the result of the callable it holds to the return type it is specified with. The callable just has to be able to accept the argument list of the std::function
.
To illustrate this, if we were to add a third argument to one of the std::function
types,
std::vector<std::variant<
std::function< int (const std::vector<float>&, int) >,
std::function< float (const std::vector<float>&, int, int) >
>> func_vector;
then there would be no ambiguity. The lambda is a valid initializer for only one variant member now.
The workarounds are to either cast to the exact function type you wish to hold, or tell the emplaced variant which option it should initialize, for instance:
func_vector.emplace_back( std::in_place_index<0>, [] (const std::vector<float>& ret, int ) { return ret.size(); });
std::variant
's converting constructor behaves similar to overload resolution in order to determine which type to construct.
If you had two functions
void f(std::function< int (const std::vector<float>&, int) >);
void f(sstd::function< float (const std::vector<float>&, int) >);
then the call
f([] (const std::vector<float>& ret, int index) { return ret.size(); })
would be ambiguous as well, because the constructor of std::function
participates in overload resolution if the argument is callable with types const std::vector<float>&
and int
and return type implicitly convertible to int
or float
. The types are not required to be exactly the same.
Both overloads are therefore possible for your lambda and since each of them requires one user-defined conversion (from lambda type to std::function
), the overload resolution is ambiguous.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With