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

时间:2017-08-06 17:17:06

标签: c++ lambda future c++17 variant

我正在玩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?

以下是编译错误:

  

错误C2665'std :: variant&lt; std :: function&lt; std :: future&lt; void&gt;   (int)&gt ;,std :: function&lt; void(int)&gt;&gt; :: variant':2个重载中没有一个   可以转换所有参数类型

好的,我们可以将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>如此特别?

1 个答案:

答案 0 :(得分:16)

variant的转换构造函数模板使用重载决策来确定构造对象应该具有哪种类型。特别是,这意味着如果对这些类型的转换同样好,则构造函数不起作用;在你的情况下,如果std::function特化中只有一个可以从你的参数中构造出来,那么它就可以工作。

那么function<...>何时可以从给定的参数中构造出来?从C ++ 14开始,如果参数可以使用参数类型调用,并生成一个convertible to the return type的类型。请注意,根据此规范,如果返回类型为void,则任何内容都会变为(any expression can be converted to void with static_cast)。如果您有function返回void,您传入的仿函数可以返回任何内容 - 这是一个功能,而不是错误!这也是function<void(int)>适用于f1的原因。另一方面,future<int>未转换为future<void>;因此只有function<void(int)>是可行的,并且变体的索引是1。

但是,在第二种情况下,lambda返回future<void>,它可以转换为future<void>void。如上所述,这会导致两个function专业化都可行,这就是variant无法决定构建哪一个的原因。

最后,如果您将返回类型调整为int,则会避免整个void转换问题,因此一切都按预期工作。