Почему компилятор разрешает доступ к массиву за пределами границ даже с индексом constexpr?

Например, если у нас есть std::array, и мы создаем экземпляр элемента, который выходит за границы, используя constexpr, компилятор не сообщит об ошибке:

constexpr int EvaluateSpecialArrayIndex(int a)
{ return a * sizeof(int); }

array<int, 5> arr;

cout << arr[98] << endl; //compiles fine

cout << arr[EvaluateSpecialArrayIndex(4)] << endl; //the same as above

Нельзя ли как-то это ограничить?


person AnArrayOfFunctions    schedule 19.12.2014    source источник
comment
примечание: стандарт не позволяет отклонять arr[n], потому что UB не вызывается до тех пор, пока выполнение не достигнет этой точки; например этот код может быть в функции, которая никогда не вызывается.   -  person M.M    schedule 19.12.2014
comment
@MattMcNabb Это может быть не так просто.   -  person GSerg    schedule 19.12.2014
comment
@GSerg UB может путешествовать во времени после удара, но не в том случае, если он не поражен (сначала звучит парадоксально, но на самом деле это не так, как показывает первый ответ). Я думаю, вы могли бы возразить, что если путь кода гарантированно достигает arr[98], то UB может вернуться во времени к этапу компиляции.   -  person M.M    schedule 19.12.2014


Ответы (4)


Чтобы гарантировать, что constexpr функции оцениваются во время компиляции, вы должны заставить их быть, сделав их результат constexpr. Например:

#include <array>

int
main()
{
    constexpr std::array<int, 5> arr{1, 2, 3, 4, 5};
    int i = arr[6];  // run time error
}

Однако:

#include <array>

int
main()
{
    constexpr std::array<int, 5> arr{1, 2, 3, 4, 5};
    constexpr int i = arr[6];  // compile time error
}

К сожалению, чтобы это действительно работало, std::array должно соответствовать спецификации C++14, а не спецификации C++11. Поскольку спецификация C++11 не помечает const перегрузку std::array::operator[] с помощью constexpr.

Так что в С++ 11 вам не повезло. В C++14 вы можете заставить его работать, но только если и array, и результат вызова оператора индекса объявлены как constexpr.

Пояснение

Спецификация С++ 11 для индексации массива гласит:

                reference operator[](size_type n);
          const_reference operator[](size_type n) const;

И спецификация С++ 14 для индексации массива гласит:

                reference operator[](size_type n);
constexpr const_reference operator[](size_type n) const;

т.е. constexpr был добавлен к перегрузке const для C++14.

Обновить

И спецификация С++ 17 для индексации массива гласит:

constexpr       reference operator[](size_type n);
constexpr const_reference operator[](size_type n) const;

Теперь цикл завершен. Вселенная может быть вычислена во время компиляции. ;-)

person Howard Hinnant    schedule 19.12.2014
comment
Значение constexpr без const изменилось с C++11 на 14. - person Yakk - Adam Nevraumont; 19.12.2014
comment
Таким образом, не-constность operator[] в std::array, то есть constexpr, не влияет на C++11. Он был добавлен в C++14 из-за изменения значения и возможности видоизменять данные в выражениях constexpr, что означает, что не-const constexpr теперь означает, что это не-const, но constexpr. - person Yakk - Adam Nevraumont; 19.12.2014
comment
@Yakk: Итак, мое пояснение - это попытка пояснить, что операторы индекса из C++ 11 не были украшены constexpr. И constexpr был добавлен только к перегрузке const в C++14. Изменения языка C++14 для constexpr здесь не повлияли. - person Howard Hinnant; 19.12.2014
comment
Странно, как это было реализовано, поскольку функции constexpr не могут выполнять static_assert для параметров. - person AnArrayOfFunctions; 19.12.2014
comment
Я также считаю, что это только для массивов, оцениваемых во время компиляции, я прав? - person AnArrayOfFunctions; 19.12.2014

Если вы знаете индекс массива во время компиляции, вы можете использовать std::get с индексом, и это приведёт к ошибке компиляции, если вы выйдете за пределы

std::array<int, 4> a{{1,2,3,4}};
std::get<4>(a); // out of bounds, fails to compile

Ошибка, которую я получаю от gcc-4.9, заканчивается:

error: static assertion failed: index is out of bounds
       static_assert(_Int < _Nm, "index is out of bounds");

std::get работает только с индексами константных выражений (индекс является аргументом шаблона), поэтому с std::array он всегда может обнаружить выход за пределы во время компиляции.

person Ryan Haining    schedule 19.12.2014

Доступ к массиву std::array такой же, как и для обычного C-массива, он никогда не проверяет правильность индекса, он просто вызывает UB, если он выходит за пределы допустимого диапазона. Если вам нужны ограничения, используйте std::array::at(), который создает исключение std::out_of_range() для значений, превышать границы массива.

arr.at(EvaluateSpecialArrayIndex(4)); // terminate called after throwing
                                      // an instance of 'std::out_of_range'

Если вам нужна ошибка времени компиляции, используйте std::get:

std::get<EvaluateSpecialArrayIndex(4)>(arr); // error: static_assert failed
                                             // "index is out of bounds"
person 0x499602D2    schedule 19.12.2014
comment
@MattMcNabb Существует constexpr версия at() в C++14, но, возможно, он не использует этот стандарт. - person 0x499602D2; 19.12.2014
comment
@Matt std::get перегружен для std::array. Это время компиляции. Смотрите ответ Райана. - person ; 19.12.2014
comment
@MattMcNabb Да, ты прав. std::get это еще один вариант. - person 0x499602D2; 19.12.2014
comment
И что, если это «constexpr», все равно ошибка выдается во время выполнения. Очень удобно использовать шаблоны, но я также хочу поместить lvalue в качестве индексов. - person AnArrayOfFunctions; 19.12.2014
comment
Насколько я понимаю, если arr является массивом статической или автоматической длительности, arr+x-arr равно constexpr (равно x), если x равно constexpr, а x не больше, чем длина (количество элементов) arr. Будет ли он считаться не constexpr, если x превышает длину arr? - person supercat; 11.05.2017

Простой ответ, потому что для std::array было бы очень дорого иметь отдельные перегрузки constexpr для проверки подобных вещей. Если вы хотите, вы можете написать свою собственную оболочку вокруг std::array, которая предлагает доступ с проверкой компиляции для констант времени компиляции. Что-то вроде:

template<typename T, size_t S>
class safeArray
{
    std::array<T, S> m_arr;
    public:
    template<size_t A>
    T& safeAt() { 
        static_assert(A < S, "Index out of bounds");
        return m_arr[A]; 
    }
    // other funcs as needed
};

Затем вы можете сделать что-то вроде:

safeArray<int, 5> arr;
cout << arr.safeAt<98>() << endl; // compile error

Однако это может генерировать множество функций. В большинстве случаев, если ваш дизайн надежен, вам никогда не понадобится этот тип проверки.

person Red Alert    schedule 19.12.2014
comment
Если вы окажетесь в зависимой области, где у вас есть safeArray<T, 5> arr, вам нужно будет назвать ее как arr.template safeAt<98>() fyi. Вот почему std::get — бесплатная функция - person Ryan Haining; 19.12.2014