Убрать некоторые перегрузки для `std::array` с нулевым размером
В текущем варианте функции front, back, operator[] имеют неопределенное поведение, если array.size() == 0.
Данное решение видимо было сделано, чтобы специализации std::array<T, 0> удовлетворяли требованию SequenceContainer. Но точно ли массив нулевого размера является sequence container? Тем более, на cppreference указано, что для std::array есть исключения.
Удаление перегрузок данных перегрузок имеет два преимущества:
- Убирается UB там, где это легко и удобно заменяется ошибкой компиляции, как для реализаций стандартной библиотеки, так и для её пользователей.
- Данные функции можно сделать
noexcept, так как в них больше нет UB.
Недостаток один:
- Специализации
std::array<T, 0>перестают удовлетворять требованиюSequenceContainer. Кажется, это не является большой потерей.
Псевдокод реализации:
template <typename T, size_t N>
struct array
{
...
reference front() noexcept requires N != 0;
const_reference front() const noexcept requires N != 0;
reference back() noexcept requires N != 0;
const_reference back() const noexcept requires N != 0;
reference operator[](size_type pos) noexcept requires N != 0;
const_reference operator[](size_type pos) const noexcept requires N != 0;
...
};
Если сделать noexcept(N != 0), то оба свойства будут присутствовать. Или так нельзя?
Если сделать
noexcept(N != 0), то оба свойства будут присутствовать. Или так нельзя?
UB не исчезнет, noexcept'ность конечно появится
А для каких целей используется std::array с нулевой длинной?
template<size_t N>
constexpr std::optional<int> first_worker_weight(const std::array<int, N>& arr) {
if constexpr(N == 0) {
return std::nullopt;
}
return arr[0]; // or arr.front()
}
template<size_t N>
constexpr int sum_workers_weight(const std::array<int, N>& arr) {
int result = 0;
for (size_t i = 0; i < N; ++i) {
result += arr[i];
}
return result;
}
Код такого вида корректен, но его компиляция будет сломана при N = 0. Чтобы чинить компиляцию такого кода, придется использовать: 1) at() с доп. проверкой внутри 2) итератор, который убьет читаемость в некоторых случаях. К тому же не ясно сколько кода придется починить .
Если с front и back понятно, что легко ошибиться и можно оградиться от ошибок, то не понятно зачем ломать интерфейс с operator[] ? Сейчас std::array<T,N>::operator[] имеет УБ, если обращаться к несуществующему элементу. Для этого случая логика такая же. Кто пользуется индексами сам проверяет их корректность.
p.s. If size() is 0, data() may or may not return a null pointer. может и тут починить?
Чтобы чинить компиляцию такого кода, придется использовать: 1) at() с доп. проверкой внутри 2) итератор, который убьет читаемость в некоторых случаях.
Или поставить else :)
Или поставить
else:)
На мой взгляд, это органично только для первого примера, использовать такое везде не очень удобно.
Код такого вида корректен, но его компиляция будет сломана при N = 0.
Да, есть проблема с тем, что в C++ везде есть код по типу:
if (xxx) {
// обычный код...
} else {
// тоже обычный код, но который при (xxx == true) генерировал бы ЖОСКИЙ UB
}
когда рантаймовые ошибки переводятся в ошибки компиляции (как в этом issue), за это придется заплатить:
if constexpr (xxx) {
// ...
} else {
// ...
}
Но во многих местах мы не можем просто добавить constexpr, потому что xxx может не быть вычислимым на этапе компиляции.
Я бы предложил сделать некую статическую проверку для компилятора, который будет выдавать warning, если рантайм в теории может привести к UB. Но таких проверок и так несколько сотен, и ни одна не предложит переделать такое:
#include <iostream>
#include <array>
int main() {
std::array<int, 128> arr;
int v;
std::cin >> v; // вводится 100500
std::cout << arr[v] << std::endl;
}