【问题标题】:Direct initialization fails with class derived from std::tuple, while it works for std::pair从 std::tuple 派生的类直接初始化失败,而它适用于 std::pair
【发布时间】:2021-01-30 13:58:02
【问题描述】:

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

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

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>'
    // ...
}

有谁知道,为什么会这样?

有没有办法解决这个问题?

我在 Compiler Explorer 上使用 clang、gcc 和 msvc 对此进行了测试。

感谢您的帮助,

问候,

Zoppo

【问题讨论】:

标签: c++


【解决方案1】:

我从将指令分成两部分开始(使用旧式初始化来绕过与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&lt;0, _Elements...&gt; 和私有地来自_Head_base&lt;_Idx, _Head&gt;(多重继承,参数个数递归)。现在是时候寻找这些类的非静态数据成员了。第一堂课看起来很乱,以至于我失败了。然而,第二个在它的声明中有这一行:

_Head _M_head_impl;

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

现在是时候找到合适的结构化绑定分解规则了。我们转到https://en.cppreference.com/w/cpp/language/structured_binding,然后直接转到“案例 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 link 并看到 std::tuple 是上述案例 3 的一个例外。编译器必须以自己特殊的方式处理标准元组和类似的类。换句话说,std::tuple 和类似元组的类由 Case 2 处理,但任何其他类或结构都由更具限制性(并且非常笼统)Case 3 处理>.

因此,要使您的程序编译,您必须使您的类“类元组”,如上面引用的源代码中所述。 我根本不知道这是否可能——我想这值得提出一个单独的问题。 @yakk-adam-nevraumont 的回答中描述了如何做到这一点

【讨论】:

  • 非常感谢您提供的详细信息,现在我明白为什么它不能工作了。
【解决方案2】:

Pair 是具有公共成员的聚合,元组不是。

您的继承自对正在使用聚合结构化绑定。

元组使用元组机制。某些元组机制不适用于继承是有充分理由的。

所以你需要专门化std::tuple_size&lt;your_type&gt; 来公开元组大小。

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>>{};
}

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

是的,这很糟糕。

【讨论】:

  • std::get 自动工作,因为该类将std::tuple 作为基本情况,对吧?)
  • @user2 是的,重载解析会考虑基数,而模板特化则不会。
  • 非常感谢......但我想我不会在标准命名空间中实现某些东西(afaik C++ 标准不允许它)。所以我想我会咬紧牙关,实现一个函数,并在用结构替换我的类型后在无法编译的任何地方使用它。
  • @zoppo 您根据您的类型对类型的某些特征进行专门化是明确允许的,并且是设计使然。这是你应该做的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-16
  • 1970-01-01
  • 2018-01-13
  • 2014-08-31
相关资源
最近更新 更多