std::tuple 具有泛型类型,如 boost::any

Flo*_*mid 5 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;
    }
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' 等等等等......

Bar*_*rry 3

让我们来看看重载解析:

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