Индексирование массива / арифметика указателя c не выполняется в контексте constexpr - PullRequest
0 голосов
/ 27 февраля 2020

У меня есть векторная реализация, и я пытаюсь поместить сохраненные значения T в оболочку.

Оболочка имеет ту же структуру памяти, что и T, поэтому арифметика индексации / указателя массива c на T*, кажется, работает правильно во время выполнения. Однако при попытке сделать то же самое в контексте constexpr [g cc, clang, msvc] все отклоняют код. Также обратите внимание, что data()[0] работает во всех случаях, но data()[1], data()[2] et c не удается.

Должно ли это иметь место? Почему компилятор решает, что хорошо делать арифметику указателей c для SimpleVector, но не для SimplerVector2? Кроме того, безопасно ли использовать арифметику указателя времени выполнения c (которая работает)?

#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

(Примечание: Причина, по которой я пытаюсь обернуть значения, заключается в том, что я могу сделать option_storage , поэтому не требуется T для конструирования по умолчанию)

Ответы [ 2 ]

3 голосов
/ 27 февраля 2020

Для SimpleVector возвращается указатель на элемент массива. Компилятор знает, сколько элементов содержится в массиве, и позволяет вам получить доступ к любому из этих элементов в составе функции constexpr.

SimpleVector2::data возвращает указатель на одно двойное значение. Вы можете разыменовать его с помощью * или получить доступ к первому элементу с помощью [0], но если вы попытаетесь получить доступ к элементу [1], вы столкнетесь с неопределенным поведением, поскольку возвращаемый указатель относится к одному значению, а не к массиву. В этом случае SimpleVector2, вероятно, имеет тот же размер, что и double, так что вы можете избежать неприятностей во время выполнения. Однако любое использование Undefined Behavior недопустимо в constexpr, что приводит к ошибке компиляции.

Одно из возможных изменений - если SimpleVector2::data вернет Wrapper по значению, указателю или ссылке. Добавление метода преобразования operator T сделает его использование в основном прозрачным.

2 голосов
/ 27 февраля 2020

Это потому, что вы возвращаете один элемент 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);
...