ideas icon indicating copy to clipboard operation
ideas copied to clipboard

Убрать некоторые перегрузки для `std::array` с нулевым размером

Open unterumarmung opened this issue 4 years ago • 7 comments

В текущем варианте функции 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;
...
};

unterumarmung avatar Aug 16 '21 19:08 unterumarmung

Если сделать noexcept(N != 0), то оба свойства будут присутствовать. Или так нельзя?

tomilov avatar Aug 17 '21 17:08 tomilov

Если сделать noexcept(N != 0), то оба свойства будут присутствовать. Или так нельзя?

UB не исчезнет, noexcept'ность конечно появится

unterumarmung avatar Aug 17 '21 17:08 unterumarmung

А для каких целей используется std::array с нулевой длинной?

maksimus1210 avatar Aug 19 '21 12:08 maksimus1210

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. может и тут починить?

gleb-kov avatar Aug 21 '21 17:08 gleb-kov

Чтобы чинить компиляцию такого кода, придется использовать: 1) at() с доп. проверкой внутри 2) итератор, который убьет читаемость в некоторых случаях.

Или поставить else :)

unterumarmung avatar Aug 21 '21 17:08 unterumarmung

Или поставить else :)

На мой взгляд, это органично только для первого примера, использовать такое везде не очень удобно.

gleb-kov avatar Aug 23 '21 10:08 gleb-kov

Код такого вида корректен, но его компиляция будет сломана при 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;
}

Izaron avatar Sep 05 '21 19:09 Izaron