无法使用各种 lambda 表达式初始化 std::variant

Cannot initialize std::variant with various lambda expressions

我正在玩 std::variant, lambdasstd::future,当我试图将它们组合在一起时得到了非常奇怪的结果。以下是示例:

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?

这里是编译错误:

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

好的,让我们将 variant 的项目签名从返回 void 更改为 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)

这到底是怎么回事?为什么 std::future<void> 如此特别?

variant 的转换构造函数模板使用重载决策来确定构造对象应具有的类型。特别是,这意味着如果对这些类型的转换同样好,则构造函数不起作用;在您的情况下,如果 std::function 专业之一可以从您的论点中构造出来,它就可以工作。

那么什么时候 function<...> 可以从给定的论证中构造出来?从 C++14 开始,如果参数可使用参数类型调用并生成 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 的类型。如果你有一个 function returning void,你传入的仿函数可以 return 任何东西——这是一个特性,而不是一个错误!这也是为什么function<void(int)>适用于f1的原因。另一方面,future<int> 不会转换为 future<void>;因此只有 function<void(int)> 是可行的,变体的索引是 1.

然而,在第二种情况下,lambda returns future<void> 可转换为 future<void>void。如上所述,这导致两个 function 专精都可行,这就是 variant 无法决定构建哪个的原因。

最后,如果将 return 类型调整为 int,整个 void 转换问题就可以避免,所以一切都按预期进行。