数组 indexing/Pointer 运算在 constexpr 上下文中失败
Array indexing/Pointer arithmetic fails in a constexpr context
我有一个矢量实现并试图将存储的值 T
放在包装器中。
包装器具有与 T
相同的内存布局,因此在 T*
上进行数组 indexing/pointer 运算似乎在运行时可以正常工作。但是,当尝试在 constexpr
上下文中执行相同操作时,[gcc、clang、msvc] 都拒绝该代码。
另请注意 data()[0]
在所有情况下都有效,但 data()[1]
、data()[2]
等会失败。
应该是这样吗?为什么编译器决定对 SimpleVector
进行指针运算是可以的,但对 SimplerVector2
则不行?
此外,运行时指针算法(似乎有效)使用安全吗?
#include <array>
#include <cassert>
struct Wrapper
{
double b;
};
struct SimpleVector
{
std::array<double, 100> values;
constexpr const double* data() const { return &values[0]; }
};
struct SimpleVector2
{
std::array<Wrapper, 100> values;
constexpr const double* data() const { return &values[0].b; }
};
int main()
{
{
constexpr SimpleVector my_vec{1, 2, 3, 4, 5, 6};
assert(my_vec.data()[0] == 1); // OK
assert(my_vec.data()[1] == 2); // OK
assert(my_vec.data()[2] == 3); // OK
static_assert(my_vec.data()[0] == 1); // OK
static_assert(my_vec.data()[1] == 2); // OK
static_assert(my_vec.data()[2] == 3); // OK
}
{
constexpr SimpleVector2 my_vec{1, 2, 3, 4, 5, 6};
assert(my_vec.data()[0] == 1); // OK
assert(my_vec.data()[1] == 2); // OK
assert(my_vec.data()[2] == 3); // OK
// OK!!
static_assert(my_vec.data()[0] == 1);
// FAILS! read of dereferenced one-past-the-end pointer
// is not allowed in a constant expression
static_assert(my_vec.data()[1] == 2);
// FAILS! cannot refer to element 2 of non-array object
// in a constant expression
static_assert(my_vec.data()[2] == 3);
}
return 0;
}
实时代码:https://godbolt.org/z/u_Mgtg
(注意:我尝试包装值的原因是我可以做 optional_storage,因此不需要 T
是默认可构造的)
对于SimpleVector
,指针returned指向数组的一个元素。编译器知道数组中包含多少个元素,并允许您访问任何这些元素作为 constexpr 函数的一部分。
SimpleVector2::data
returns 指向单个双精度值的指针。您可以使用 *
取消引用它,或使用 [0]
访问第一个元素,但是如果您尝试访问元素 [1]
,您 运行 进入未定义行为,因为 returned 指针指向单个值,而不是数组。在这种情况下,SimpleVector2
可能与 double
大小相同,因此您可以在 运行 时摆脱它。但是,在 constexpr
中不允许使用任何未定义行为,这会给您带来编译器错误。
一个可能的变化是,如果 SimpleVector2::data
将 return 通过值、指针或引用 Wrapper
。添加 operator T
转换方法将使其使用大部分透明。
这是因为您返回的是单个元素 b
而不是指向数组的指针。
如果您这样做,这将起作用:
struct SimpleVector2
{
std::array<Wrapper, 100> values;
constexpr const Wrapper* data() const { return &values[0]; }
};
//...
static_assert(my_vec.data()[0].b == 1);
static_assert(my_vec.data()[1].b == 2);
static_assert(my_vec.data()[2].b == 3);
但你可能想要这个:
struct SimpleVector2
{
std::array<Wrapper, 100> values;
constexpr const double& operator[] (size_t n) const { return values[n].b; }
};
//...
static_assert(my_vec[0] == 1);
static_assert(my_vec[1] == 2);
static_assert(my_vec[2] == 3);
我有一个矢量实现并试图将存储的值 T
放在包装器中。
包装器具有与 T
相同的内存布局,因此在 T*
上进行数组 indexing/pointer 运算似乎在运行时可以正常工作。但是,当尝试在 constexpr
上下文中执行相同操作时,[gcc、clang、msvc] 都拒绝该代码。
另请注意 data()[0]
在所有情况下都有效,但 data()[1]
、data()[2]
等会失败。
应该是这样吗?为什么编译器决定对 SimpleVector
进行指针运算是可以的,但对 SimplerVector2
则不行?
此外,运行时指针算法(似乎有效)使用安全吗?
#include <array>
#include <cassert>
struct Wrapper
{
double b;
};
struct SimpleVector
{
std::array<double, 100> values;
constexpr const double* data() const { return &values[0]; }
};
struct SimpleVector2
{
std::array<Wrapper, 100> values;
constexpr const double* data() const { return &values[0].b; }
};
int main()
{
{
constexpr SimpleVector my_vec{1, 2, 3, 4, 5, 6};
assert(my_vec.data()[0] == 1); // OK
assert(my_vec.data()[1] == 2); // OK
assert(my_vec.data()[2] == 3); // OK
static_assert(my_vec.data()[0] == 1); // OK
static_assert(my_vec.data()[1] == 2); // OK
static_assert(my_vec.data()[2] == 3); // OK
}
{
constexpr SimpleVector2 my_vec{1, 2, 3, 4, 5, 6};
assert(my_vec.data()[0] == 1); // OK
assert(my_vec.data()[1] == 2); // OK
assert(my_vec.data()[2] == 3); // OK
// OK!!
static_assert(my_vec.data()[0] == 1);
// FAILS! read of dereferenced one-past-the-end pointer
// is not allowed in a constant expression
static_assert(my_vec.data()[1] == 2);
// FAILS! cannot refer to element 2 of non-array object
// in a constant expression
static_assert(my_vec.data()[2] == 3);
}
return 0;
}
实时代码:https://godbolt.org/z/u_Mgtg
(注意:我尝试包装值的原因是我可以做 optional_storage,因此不需要 T
是默认可构造的)
对于SimpleVector
,指针returned指向数组的一个元素。编译器知道数组中包含多少个元素,并允许您访问任何这些元素作为 constexpr 函数的一部分。
SimpleVector2::data
returns 指向单个双精度值的指针。您可以使用 *
取消引用它,或使用 [0]
访问第一个元素,但是如果您尝试访问元素 [1]
,您 运行 进入未定义行为,因为 returned 指针指向单个值,而不是数组。在这种情况下,SimpleVector2
可能与 double
大小相同,因此您可以在 运行 时摆脱它。但是,在 constexpr
中不允许使用任何未定义行为,这会给您带来编译器错误。
一个可能的变化是,如果 SimpleVector2::data
将 return 通过值、指针或引用 Wrapper
。添加 operator T
转换方法将使其使用大部分透明。
这是因为您返回的是单个元素 b
而不是指向数组的指针。
如果您这样做,这将起作用:
struct SimpleVector2
{
std::array<Wrapper, 100> values;
constexpr const Wrapper* data() const { return &values[0]; }
};
//...
static_assert(my_vec.data()[0].b == 1);
static_assert(my_vec.data()[1].b == 2);
static_assert(my_vec.data()[2].b == 3);
但你可能想要这个:
struct SimpleVector2
{
std::array<Wrapper, 100> values;
constexpr const double& operator[] (size_t n) const { return values[n].b; }
};
//...
static_assert(my_vec[0] == 1);
static_assert(my_vec[1] == 2);
static_assert(my_vec[2] == 3);