亲爱的程序员小伙伴们,
下面的代码让我有些头疼。它尝试将“通用”对象(=可以从任何东西构造的对象)添加到元组,然后复制该元组。
#include <tuple>
#include <iostream>
#include <typeinfo>
struct anything
{
anything()
{}
anything(const anything&)
{
std::cout << "Called copy c'tor" << std::endl;
}
template<class T>
anything(T arg)
{
std::cout << "Called c'tor with with argument of type " << typeid(arg).name() << std::endl;
// new T(arg); // causes stack overflow
}
};
int main()
{
std::tuple<anything> t;
//
std::cout << "Copy constructing t2, expecting copy c'tor to be called." << std::endl;
std::tuple<anything> t2(t);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
使用 VS 2015 Update 2 它甚至无法编译,该行
std::tuple<anything> t2(t);
Run Code Online (Sandbox Code Playgroud)
在 tuple.h 深处触发编译器错误。使用 gcc 5.3.1 它可以编译,但输出不是我所期望的:
复制构造 t2,期望调用复制 c'tor。
调用复制 c'tor
调用 c'tor,参数类型为 St5tupleIJ8anythingEE
我不明白的是最后一行,即为什么使用 std::tuple 作为参数调用模板化构造函数?
这实际上是一个现实世界的问题。在我的应用程序中,我使用了一个 boost::signal 签名
typedef boost::type_erasure::any
<boost::mpl::vector<
boost::type_erasure::typeid_<>,
boost::type_erasure::copy_constructible<>,
boost::type_erasure::less_than_comparable<>,
boost::type_erasure::equality_comparable<>,
boost::type_erasure::relaxed
>> KeyType;
boost::signals2::signal<void(const KeyType&)>
Run Code Online (Sandbox Code Playgroud)
Boost 信号在使用它调用槽函数之前在内部将参数包装在一个元组中,这最终导致堆栈溢出,因为元组 c'tor 以元组为参数调用 any c'tor,然后 any c'tor 调用元组 c'tor 与 'any of tuple' 等等等等......
让我们来看看重载解析:
std::tuple<anything> t2(t);
Run Code Online (Sandbox Code Playgroud)
我们有三个可行的构造函数可供使用:
explicit tuple( const Types&... args ); // (2) with Types = [anything]
template< class... UTypes >
explicit tuple( UTypes&&... args ); // (3) with UTypes = [std::tuple<anything>&]
tuple( const tuple& other ) = default; // (8)
Run Code Online (Sandbox Code Playgroud)
其中有这些参数列表:
tuple(const anything& ); // (2)
tuple(std::tuple<anything>& ); // (3)
tuple(std::tuple<anything> const& ); // (8)
Run Code Online (Sandbox Code Playgroud)
(2)涉及用户定义的转换,而(3)和(8)是完全匹配的。当涉及到引用绑定时:
如果 [...] S1 和 S2 是引用绑定 (8.5.3),并且除了顶级 cv 之外,引用引用的类型是相同的类型,则标准转换序列 S1 是比标准转换序列 S2 更好的转换序列-限定符,并且 S2 初始化的引用所引用的类型比 S1 初始化的引用所引用的类型更具 cv 限定性。
所以(3)是首选 - 因为它的简历资格比(8). 该构造函数调用anything(std::tuple<anything> ).
就解决方案而言,您需要的是(3)在这种情况下不被考虑 - 我们需要使其不是一个可行的选择(因为(8)已经首选(2))。目前,最简单的事情就是创建构造函数explicit:
template<class T>
explicit anything(T arg) { ... }
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为(3)是根据 指定的is_convertible<>,它将在explicit转换时返回 false。然而,这目前被认为是一个缺陷,并且将来可能会改变 - 因为毕竟,我们在这里显式构建每个方面,所以explicit仍然应该考虑构造函数!
一旦发生这种情况,就直接复制构建而言,您就有点不走运了。你必须做一些事情,比如禁用你的anything构造函数tuple?看来……不太好。但在这种情况下,标记该构造函数explicit仍然适用于复制初始化:
std::tuple<anything> t2 = t;
Run Code Online (Sandbox Code Playgroud)
由于上述相同的缺陷,即使没有标记anything构造函数,它现在也可以工作。explicit