使用零大小数组的std::swap()编译错误

Compile error for std::swap() with zero-size arrays?

本文关键字:swap 错误 编译 std 小数 数组      更新时间:2023-10-16

我试图避免在数组长度变为0的情况下为类模板添加显式专门化。结果是std::swap()无法处理它:

#include <algorithm>
int main() {
    int a[0], b[0];
    std::swap(a, b);   // g++-4.8 compile error
}

我认为应该有一些SFINAE来防止这样的错误,不是吗?显然,在这种情况下,什么都不做是正确的。

如果标准强制std::swap()引发编译器错误,我可以手动添加一个编译时if来检查非类型模板参数std::size_t N是否为0吗?

编辑

实际上,std::array<T, 0>是一个特殊的模板,它避免声明一个大小为零的数组。From gcc-4.8.2/libstdc++-v3/include/std/array:

template<typename _Tp, std::size_t _Nm>
    struct __array_traits
    {
      typedef _Tp _Type[_Nm];
      static constexpr _Tp&
      _S_ref(const _Type& __t, std::size_t __n) noexcept
      { return const_cast<_Tp&>(__t[__n]); }
    };
 template<typename _Tp>
   struct __array_traits<_Tp, 0>
   {
     struct _Type { };
     static constexpr _Tp&
     _S_ref(const _Type&, std::size_t) noexcept
     { return *static_cast<_Tp*>(nullptr); }
   };

这是c++中未定义的定义大小为零的数组的行为。

c++标准n3337 § 8.3.4/1

如果存在常量表达式(5.19),则该表达式应为整型常量表达式及其值必须大于零。

使用array new: new[]: 动态创建零大小的数组是有效的。c++标准n3337 § 5.3.4/6

noptr-new-declarator中的每个常量表达式都应该是积分常数表达式(5.19)并严格求值为a积极的价值。noptr-new-declarator中的表达式应该是of整数类型、无作用域枚举类型或类类型单个非显式的积分或无作用域转换函数枚举类型存在(12.3)。如果表达式是类类型,表达式通过调用转换函数进行转换,并且用转换的结果代替原来的结果表达式。

c++标准n3337 § 5.3.4/7

当noptr-new-declarator中表达式的值为0 时,分配函数被调用来分配一个数组元素。如果该表达式的值小于零或诸如此类分配对象的大小将超过实现定义的限制,或者如果new-初始化式是带括号的初始化子句数超过的初始化列表要初始化的元素数,没有存储空间New-expression通过抛出类型为的异常而终止是否匹配std::bad_array_new_length类型的处理程序(15.3)(18.6.2.2)。

免责声明:已经解释过c++不允许零长度数组,因此创建/交换它们是未定义的行为。零长度数组作为扩展被gcc支持。


编译错误没有说明长度为零的数组。如果您启用-pedantic,则会有关于它们的警告,但不会完全拒绝它们。相反,编译器会抱怨一个无效的赋值。原因很有趣。

std::swap对数组类型有重载。但是,由于零长度数组不被认为是有效的数组类型,因此在传入零长度数组时不会选择此重载。这可以用下面的代码来演示:

template<typename T, std::size_t N>
void foo(T const (&)[N])
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template<typename T>
void foo(T const&)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

传递一个非零长度数组给foo,输出为

void foo(const T (&)[N]) [with T = int; long unsigned int N = 10ul]

现在传递一个零长度数组给foo,输出改变

void foo(const T&) [with T = int [0]]

现场演示


现在,回到错误的原因。对于非数组的std::swap实现会将一个参数移动/复制到一个局部变量,然后将第二个参数移动/复制到第一个参数,最后将局部变量移动/复制到第二个参数。就是这一系列的move/copy初始化和赋值操作出了问题。

T temp = move(arg1);
arg2 = move(arg2);
arg1 = move(temp);

上述语句在T=int[0]时无效,因此出现错误。


解决这个问题最简单的方法是使用std::array 。它对零长度数组有特殊的支持,并且可以正确地交换它们。

否则,如果您想继续依赖不可移植的gcc扩展,我会为swap制作一个包装器,该包装器具有接受零长度数组的过载。在所有其他情况下,包装器将调用std::swap

template<typename T>
void my_swap(T& arg1, T& arg2)
{
    using std::swap;
    swap(arg1, arg2);
}
template<typename T, std::size_t N>
void my_swap(T (&arg1)[N], T (&arg2)[N])
{
    using std::swap;
    swap(arg1, arg2);
}
template<typename T>
void my_swap(T (&)[0], T (&)[0])
{
    // do nothing
}

现场演示