std :: tuple与泛型类型,如boost :: any

时间:2016-04-13 08:38:48

标签: c++ c++11

亲爱的程序员,

下面的代码让我有些头疼。它尝试将一个“泛型”对象(=可以从任何东西构造的对象)添加到元组,然后复制该元组。

    #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

的参数调用c'tor

我不明白的是最后一行,即为什么模板化构造函数以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,依此类推,然后开启......

2 个答案:

答案 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...>&);

显然你的模板构造函数比元组的复制构造函数更好。