Cannot initialize std::variant with various lambda expressions

大城市里の小女人 提交于 2021-02-07 11:23:07

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/45534600/cannot-initialize-stdvariant-with-various-lambda-expressions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!