2017-08-06 138 views
15

我在玩std::variant, lambdasstd::future,当我试图将它们组合在一起时,得到了超级奇怪的结果。下面举例说明:无法初始化各种lambda表达式的std :: variant

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

OK,从返回voidint让改变variant的物品签名:

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

注意:'std :: variant'是一个C++ 17特性,而不是C++ 11。 – Rakete1111

+1

你的编译错误来自第二个例子,但你的问题看起来像是从你的第一个例子。 – Barry

回答

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

然而,在第二种情况下,返回拉姆达future<void>,这可以转换为两个future<void>void。如上所述,这导致两个function专业化是可行的,这就是为什么variant无法决定构建哪一个。

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

+0

感谢您的回复。但我仍然无法想象'std :: future '可以隐式转换为'void'(或'void'为'std :: future ' - 我不确定我了解这一点)?这是否意味着什么可以转换为“无效”? –

+3

@DmitryKatkevich这是正确的,任何表达式都可以通过'static_cast'转换为'void'。否则,我们无法向'function'提供函子,当'function'产生'void'时返回一些东西,这是一个理想的特性! – Columbo