我试图避免为数组长度变为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); }
};
答案 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]]
现在,回到错误的原因。非数组的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
}