对std::tuple派生的类直接初始化失败,而对std::pair有效



有时我想用改变我通过定义的类型由一个真正的类。

例如,这里有一个示例,如何像使用类型一样使用结构体:

using t_int_pair = std::pair< int, int >;
struct s_int_pair : public std::pair< int, int >
{
using std::pair< int, int >::pair; // inherit ctor
};
void foo()
{
auto [a1, a2] = t_int_pair{ 0, 0 };
auto [b1, b2] = s_int_pair{ 0, 0 };
// ...
}

但是:无论出于何种原因,同样的代码对std::tuple不起作用,也就是说,这会产生编译错误:
using t_int_triple = std::tuple< int, int, int >;
struct s_int_triple : public std::tuple< int, int, int >
{
using std::tuple< int, int, int >::tuple; // inherit ctor
};
void bar()
{
auto [a1, a2, a3] = t_int_triple{ 0, 0, 0 };
auto [b1, b2, b3] = s_int_triple{ 0, 0, 0 }; // ERROR: cannot decompose class type 'std::_Tuple_impl<0, int, int>'
// ...
}

有人知道为什么会这样吗?

有办法解决这个问题吗?

我用clang, gcc和msvc在编译器资源管理器上测试了这个。

任何帮助都是感激的,

问候,

Zoppo

我开始将指令分成两部分(使用老式的初始化来绕过与std::initailizer_list相关的任何问题,如果有的话):

s_int_triple s( 0, 0, 0 );
auto [b1, b2, b3] = s;

编译器错误读取:

main.cpp:27:10: error: cannot decompose class type ‘std::_Tuple_impl<1, int, int>’: its base classes ‘std::_Head_base<2, int, false>’ and ‘std::_Head_base<1, int, false>’ have non-static data members
27 |     auto [b1, b2, b3] = s;
|          ^~~~~~~~~~~~

所以我得出结论,构造函数的继承不是问题,问题在于结构化绑定。c++是一种糟糕的语言,当人们不得不看它的标准模板库时,但是在将程序加载到一个体面的IDE后,我发现在std::tuple的gcc 10,2实现中,这个类公开继承_Tuple_impl<0, _Elements...>,私下继承_Head_base<_Idx, _Head>(多重继承,参数数量递归)。现在是时候寻找这些类的非静态数据成员了。第一节课看起来很乱,所以我不及格。然而,第二个语句的声明中有这一行:

_Head _M_head_impl;

宾果!这是一个非静态数据成员。

现在是时候找到合适的结构化绑定分解规则了。我们进入https://en.cppreference.com/w/cpp/language/structured_binding,直接进入"Case 3":

E的每个非静态数据成员必须是E的直接成员或与E相同的基类,并且当命名为e.name时,必须在结构化绑定的上下文中格式良好。E可能没有匿名的工会成员。标识符的数目必须等于非静态数据成员的数目。

所以这里我们有了答案:从std::tuple派生的类也继承了其他几个类,其中一些类定义了非静态成员。

这个现象的简单例子:

struct A
{
int x;
};
struct B: public A
{
int y;
};
int main()
{
B obj;
auto [a, b] = obj;
}

导致

main2.cpp:14:8: error: cannot decompose class type ‘B’: both it and its base class ‘A’ have non-static data members

现在我们遇到了一个真正的难题:如果std::tuple继承自不同的类,并且每个类都定义了一个非静态成员,那么为什么我们可以为元组使用结构化绑定?我们回到cppreference链接,看到std::tuple是上面描述的情形3的一个例外。编译器必须处理标准元组和类似的类以他们自己独特的方式。换句话说,std::tuple和类元组由Case 2处理,但任何其他类或结构都是由更严格的(并且非常通用的)处理的情况3.

因此,为了使您的程序编译,您必须使您的类"像元组一样",正如上面引用的源代码中所描述的那样。我根本不知道这是否可能——我想这应该问一个单独的问题。 @yakk-adam-nevraumont在回答中描述了如何做到这一点

Pair是public成员的聚合,而tuple不是。

您的inherit-from-pair正在使用聚合结构化绑定。

Tuple使用元组机制。有些元组机制不适合继承是有原因的。

所以你需要专门化std::tuple_size<your_type>来公开元组的大小。

struct s_int_triple : public std::tuple< int, int, int >
{
using std::tuple< int, int, int >::tuple; // inherit ctor
};
namespace std{
template<>
class tuple_size<::s_int_tuple>:public std::integral_constant<std::size_t, 3>{};
template< std::size_t I >
class tuple_element<I,::s_int_tuple>:public tuple_element<I, std::tuple<int,int,int>>{};
}

和结构化绑定应该可以工作。

是的,这很糟糕。