数组 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);