使用零大小数组的 std::swap() 编译错误?
Posted
技术标签:
【中文标题】使用零大小数组的 std::swap() 编译错误?【英文标题】:Compile error for std::swap() with zero-size arrays? 【发布时间】:2014-06-10 21:47:22 【问题描述】:在数组长度变为 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);
;
【问题讨论】:
在标准 C 和 C++ 中,不允许使用零大小的数组。 它能处理其他大小的数组吗?我希望它不适用于数组期间,因为您无法分配给数组。 @40two:它似乎适用于除std::swap()
调用之外的所有内容,不过......
@Stefan 在 gcc 中您会收到警告:ISO C++ 禁止零大小数组。从那里发生的事情是未定义的行为。
@Stefan:这不是灰色地带。这是明确和明确禁止的。将模板特化为不使用 0 大小的数组。
【参考方案1】:
这是在 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 中表达式的值为零时, 调用分配函数来分配一个没有 如果该表达式的值小于零等 分配对象的大小将超过 实现定义的限制,或者如果 new-initializer 是一个花括号- 初始化子句数超过 要初始化的元素数量,没有获得存储,并且 new-expression 通过抛出一个类型的异常来终止 将匹配 std::bad_array_new_length 类型的处理程序 (15.3) (18.6.2.2)。
【讨论】:
是的,new T[0]
是允许的。见 5.3.4/7。【参考方案2】:
免责声明: 已经说明 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
【讨论】:
感谢您的分析和出色的解决方法!现在我的课程在没有代码重复的情况下工作正常(请参阅我的帖子中的编辑,了解std::array
中gcc-4.8
的代码重复版本)。我认为零大小的数组最终会进入标准,因为相反是愚蠢的。
@Stefan 我不建议解决这个问题。你应该使用std::array
除了我的类是我自己版本的std::array
,所以我不想依赖工厂版本。另外,如果gcc
允许零大小的数组,它自己的std::swap()
应该知道如何处理它们。在他们修复错误之前,您的解决方法很棒;)以上是关于使用零大小数组的 std::swap() 编译错误?的主要内容,如果未能解决你的问题,请参考以下文章
在 Visual Studio 中调用 std::swap 时的 std::bad_function_call
插入数组列表时,索引(从零开始)必须大于或等于零且小于参数列表的大小