Почему компилятор допускает доступ к массиву вне границ даже с индексом 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

Разве мы не можем как-то ограничить это?

10

Решение

Чтобы убедиться, что 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,

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

осветление

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

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

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

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

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

Обновить

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

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

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

13

Другие решения

Если вы знаете индекс массива во время компиляции, вы можете использовать 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 он всегда может обнаружить за пределами во время компиляции.

7

Доступ к массиву на 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"
6

Простой ответ, потому что это будет очень дорого для 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

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

2
По вопросам рекламы [email protected]