cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
88 stars 0 forks source link

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

Open unterumarmung opened 3 years ago

unterumarmung commented 3 years ago

В текущем варианте функции front, back, operator[] имеют неопределенное поведение, если array.size() == 0. Данное решение видимо было сделано, чтобы специализации std::array<T, 0> удовлетворяли требованию SequenceContainer. Но точно ли массив нулевого размера является sequence container? Тем более, на cppreference указано, что для std::array есть исключения.

Удаление перегрузок данных перегрузок имеет два преимущества:

Недостаток один:

Псевдокод реализации:

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;
...
};
tomilov commented 3 years ago

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

unterumarmung commented 3 years ago

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

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

maksimus1210 commented 3 years ago

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

gleb-kov commented 3 years ago
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. может и тут починить?

unterumarmung commented 3 years ago

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

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

gleb-kov commented 3 years ago

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

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

Izaron commented 3 years ago

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