对 ISO C++ 标准中 std::tuple 的默认构造函数描述感到困惑

Confused by default constructor description of std::tuple in the ISO C++ Standard

本文关键字:描述 构造函数 默认 tuple C++ ISO 标准 std      更新时间:2023-10-16

标准说std::tuple具有以下成员函数

constexpr tuple();
explicit tuple(const Types&...);

有人可以解释一下std::tuple<>应该会发生什么吗?

我想标准中给出的定义应该是伪代码。标准中的许多定义就是这种情况;它包含几个口头给出的要求,但只能通过enable_if这样的技巧来满足。这似乎是一个例子,其中类似C++伪代码符号在尝试实例化这样一个空元组时实际上会导致非法C++(或者它可能只是一个遗漏)。

stdlibc++ 和 libc++ 都有针对零元素元组的明确专用化。例如,在 stdlibc++ 中:

  // Explicit specialization, zero-element tuple.
  template<>  
    class tuple<>
    {
    public:
      void swap(tuple&) noexcept { /* no-op */ }
    };

使用隐式定义的明确默认构造函数。

Libc++ 没有显式声明无参数默认构造函数。据推测,模板化构造函数随后被选为非空元组的默认构造函数。

有趣的是,这两个库在空元组具有哪些成员方面存在分歧。例如,以下内容使用 libc++ 编译,但不使用 libstdc++ 编译:

#include <tuple>
#include <memory>
int main() {
  std::tuple<> t(std::allocator_arg, std::allocator<int>());
}
我相信

这是标准中的一个小错误。显然,当Types参数包为空时,两个构造函数调用是等效的,不能重载(请参阅 C++11 第 13 节)。(进一步注意,使用 Types 的构造函数也不是成员模板 - 如果是,那么它将是合法的重载。

换句话说,此代码不会编译:

template <typename... Types>
struct Test
{
  constexpr Test() {}
  explicit Test(Types const&...) { /* etc. */ }
};
int main()
{
  Test<> a;
  Test<int> b;
}

例如,G++ v4.8 快照输出:

tt.cxx: In instantiation of ‘struct Test<>’:
tt.cxx:10:10:   required from here
tt.cxx:5:12: error: ‘Test<Types>::Test(const Types& ...) [with Types = {}]’ cannot be overloaded
   explicit Test(Types const&...) { /* etc. */ }
            ^
tt.cxx:4:13: error: with ‘constexpr Test<Types>::Test() [with Types = {}]’
   constexpr Test() {}
             ^

这可以通过使用部分专用化来修复:

template <typename... Types>
struct Test
{
  constexpr Test() {} // default construct all elements
  explicit Test(Types const&...) { /* etc. */ }
  // and all other member definitions
};
template <>
struct Test<>
{
  constexpr Test() {}
  // and any other member definitions that make sense with no types
};
int main()
{
  Test<> a;
  Test<int> b;
}

这将正确编译。

似乎标准想要一个constexpr默认构造函数,以便可以编写std::tuple<> var;而不是编写std::tuple<> var();std::tuple<> var{};,因为与其他构造函数一起使用explicit。不幸的是,它对std::tuple的定义不适用于大小为零的元组。该标准在第 20.4.2.7 节(关系运算符)中确实允许这样做,"对于任何两个零长度元组,[...]"。哎呀!:-)

乍一看,歧义只在调用它的位置很重要,然后你有正常的重载分辨率。