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

时间:2014-06-10 21:47:22

标签: c++ templates c++11 std sfinae

我试图避免为数组长度变为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>是一个专门的模板,可以避免声明零大小的数组。来自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); }
   };

2 个答案:

答案 0 :(得分:3)

这是C ++中未定义的行为,用于定义零大小的数组。

C ++标准版n3337 § 8.3.4 / 1

  

如果存在常量表达式(5.19),它应该是一个整数   常量表达式及其值应大于零。

然而,使用 array new 动态创建零大小的数组是有效的:new[]

C ++标准版n3337 § 5.3.4 / 6

  

noptr-new-declarator中的每个常量表达式都应该是一个   积分常数表达式(5.19)并严格评估   正面价值。 noptr-new-declarator中的表达式应为   整数类型,未范围的枚举类型或类型的类型   单个非显式转换函数为积分或未整合   存在枚举类型(12.3)。如果表达式是类类型,   通过调用转换函数来转换表达式,并且   转换的结果用于代替原件   表达

C ++标准版n3337 § 5.3.4 / 7

  

当noptr-new-declarator中的表达式值为零时,   调用分配函数来分配没有的数组   要素。如果该表达式的值小于零或等等   分配的对象的大小将超过   实现定义的限制,或者如果新的初始化程序是支持的 -   init-list,其初始化子句数超过   初始化的元素数量,没有获得存储和   new-expression通过抛出一个类型的异常来终止   将匹配std :: bad_array_new_length类型的处理程序(15.3)   (18.6.2.2)。

答案 1 :(得分:1)

免责声明:已经解释过C ++不允许零长度数组,因此创建/交换它们是未定义的行为。 gcc支持零长度数组作为扩展。 所以后面的所有内容可能只适用于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]]

Live demo


现在,回到错误的原因。非数组的std::swap实现将一个参数移动/复制到局部变量,然后将第二个参数移动到第一个参数,最后将局部变量复制到第二个参数。这一系列的移动/复制初始化和分配出错了。

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
}

Live demo