亲爱的程序员,
下面的代码让我有些头疼。它尝试将一个“泛型”对象(=可以从任何东西构造的对象)添加到元组,然后复制该元组。
#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;
}
使用VS 2015 Update 2,它甚至没有编译,
行std::tuple<anything> t2(t);
在tuple.h中触发编译器错误。 使用gcc 5.3.1编译,但输出不是我所期望的:
复制构造t2,期望调用copy 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&)>
Boost信号在使用它调用slot函数之前在内部将参数包装在元组中,最终导致堆栈溢出,因为元组c'tor以元组作为参数调用任何c'tor并且任何c'tor然后用'任意一个元组'调用元组c'tor,依此类推,然后开启......
答案 0 :(得分:2)
让我们通过重载决议:
std::tuple<anything> t2(t);
我们有三个可行的constructors供我们使用:
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)
哪些有这些参数列表:
tuple(const anything& ); // (2)
tuple(std::tuple<anything>& ); // (3)
tuple(std::tuple<anything> const& ); // (8)
(2)
涉及用户定义的转化,而(3)
和(8)
是完全匹配。说到引用绑定:
标准转换序列S1是比标准转换序列更好的转换序列 S2,如果S1和S2是引用绑定(8.5.3),并且引用引用的类型相同 除了顶级cv限定符之外的类型,以及由S2初始化的引用所引用的类型 比由S1初始化的引用所引用的类型更符合cv。
所以(3)
是首选 - 因为它的{cv-qual}低于(8)
。该构造函数调用anything(std::tuple<anything> )
。
就解决方案而言,您需要的是(3)
在这种情况下不予考虑 - 我们需要使其不是一个可行的选项(因为(8)
已经优先于(2)
})。 目前,最简单的方法就是建立你的构造函数explicit
:
template<class T>
explicit anything(T arg) { ... }
这是有效的,因为(3)
是根据is_convertible<>
指定的,explicit
次转化时会返回false。但是,这当前被认为是一个缺陷,将来可能会发生变化 - 毕竟,我们 明确构建了这里的每个方面,所以仍然应该考虑explicit
构造函数!
一旦发生这种情况,就直接复制构建而言,你有点不走运。你必须做一些事情,例如禁用anything
的{{1}}构造函数吗?那似乎......不太好。但在这种情况下,标记构造函数tuple
仍可用于复制初始化:
explicit
由于上述相同的缺陷,现在即使没有标记std::tuple<anything> t2 = t;
构造函数anything
也能正常工作。
答案 1 :(得分:1)
如果你看一下元组的实现,你会注意到
_Tuple_val<_This> _Myfirst; // the stored element
...
template<class _This2,
class... _Rest2,
class = typename _Tuple_enable<tuple<_This2, _Rest2...>, _Myt>::type>
explicit tuple(_This2&& _This_arg, _Rest2&&... _Rest_arg)
: _Mybase(_STD forward<_Rest2>(_Rest_arg)...),
_Myfirst(_STD forward<_This2>(_This_arg))
{ // construct from one or more moved elements
}
元组的构造函数将第一个参数传递给元组的第一个元素的构造函数。由于变量t
具有类型std::tuple<anything>
编译器,因此找到与anything
构建t
的最佳匹配 - 模板构造函数。
要复制元组,只需编写
即可std::tuple<anything> t2 = t;
UPD。
根据标准std :: tuple提供以下构造函数:
template <class... UTypes>
explicit constexpr tuple(const Types&...);
template <class... UTypes>
constexpr tuple(const tuple<UTypes...>&);
显然你的模板构造函数比元组的复制构造函数更好。