I'm playing with std::variant, lambdas
and std::future
, and got super weird results when I tried to compose them together. Here are examples:
using variant_t = std::variant<
std::function<std::future<void>(int)>,
std::function<void(int)>
>;
auto f1 = [](int) { return std::async([] { return 1; }); };
auto f2 = [](int) { return std::async([] { }); };
variant_t v1(std::move(f1)); // !!! why DOES this one compile when it SHOULDN'T?
auto idx1 = v1.index(); //equals 1. WHY?
variant_t v2(std::move(f2)); // !!! why DOESN'T this one compile when it SHOULD?
Here is the compilation error:
Error C2665 'std::variant<std::function<std::future<void> (int)>,std::function<void (int)>>::variant': none of the 2 overloads could convert all the argument types
OK, lets change variant
's items signatures from returning void
to int
:
using variant_t = std::variant<
std::function<std::future<int>(int)>,
std::function<int(int)>
>;
variant_t v1(std::move(f1)); // COMPILES (like it should)
auto idx1 = v1.index(); // equals 0
variant_t v2(std::move(f2)); // DOESN'T compile (like it should)
What the hell is going on here? Why is std::future<void>
so special?
variant
's converting constructor template employs overload resolution to determine which type the constructed object should have. In particular, this means that if the conversions to those types are equally good, the constructor doesn't work; in your case, it works iff exactly one of the std::function
specializations is constructible from your argument.
So when is function<...>
constructible from a given argument? As of C++14, if the argument is callable with the parameter types and yields a type that is convertible to the return type. Note that according to this specification, if the return type is void
, anything goes (as any expression can be converted to void
with static_cast
). If you have a function
returning void
, the functor you pass in can return anything—that's a feature, not a bug! This is also why function<void(int)>
is applicable for f1
. On the other hand, future<int>
does not convert to future<void>
; hence only function<void(int)>
is viable, and the variant's index is 1.
However, in the second case, the lambda returns future<void>
, which is convertible to both future<void>
and void
. As mentioned above, this causes both function
specializations to be viable, which is why the variant
cannot decide which one to construct.
Finally, if you adjust the return type to int
, this whole void
conversion issue is avoided, so everything works as expected.
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