Подтвердить что ты не робот

Почему non-const std:: array:: operator [] not constexpr?

Я пытаюсь заполнить 2D-массив во время компиляции заданной функцией. Вот мой код:

template<int H, int W>
struct Table
{
  int data[H][W];
  //std::array<std::array<int, H>, W> data;  // This does not work

  constexpr Table() : data{}
  {
    for (int i = 0; i < H; ++i)
      for (int j = 0; j < W; ++j)
        data[i][j] = i * 10 + j;  // This does not work with std::array
  }
};

constexpr Table<3, 5> table;  // I have table.data properly populated at compile time

Он работает нормально, table.data правильно заполняется во время компиляции.

Однако, если я изменяю простой 2D-массив int[H][W] с std::array<std::array<int, H>, W>, у меня есть ошибка в теле цикла:

error: call to non-constexpr function 'std::array<_Tp, _Nm>::value_type& std::array<_Tp, _Nm>::operator[](std::array<_Tp, _Nm>::size_type) [with _Tp = int; long unsigned int _Nm = 3ul; std::array<_Tp, _Nm>::reference = int&; std::array<_Tp, _Nm>::value_type = int; std::array<_Tp, _Nm>::size_type = long unsigned int]'
data[i][j] = i * 10 + j;
^
Compilation failed

Очевидно, я пытаюсь вызвать неконстантную перегрузку std::array::operator[], которая не является constexpr. Вопрос в том, почему это не constexpr? Если С++ 14 позволяет нам изменять переменные, объявленные в области constexpr, почему это не поддерживается std::array?

Раньше я думал, что std::array как обычный массив, только лучше. Но вот пример, где я могу использовать простой массив, но не могу использовать std::array.

4b9b3361

Ответ 1

Хорошо, это действительно упущение в стандарте. Существует даже предложение исправить это: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0107r0.pdf

[N3598] убрал неявную маркировку функций-членов constexpr как const. Однако функции-члены std::array не были пересмотрены после этого изменения, что привело к неожиданному отсутствию поддержки constexpr в интерфейсе std::arrays. Этот документ исправляет это упущение, добавляя constexpr для функций-членов std::array, которые могут поддерживать его с минимальным количеством работа.

UPD: исправлено в С++ 17: https://en.cppreference.com/w/cpp/container/array/operator_at

Ответ 2

std::array::operator[], так как С++ 14 constexpr, но также const квалифицирован:

constexpr const_reference operator[]( size_type pos ) const;
                                                      ^^^^^

Таким образом, вы должны наложить массивы, чтобы вызвать правильную перегрузку operator[]:

template<int H, int W>
struct Table
{
  //int data[H][W];
  std::array<std::array<int, H>, W> data;  // This does not work

  constexpr Table() : data{} {
    for (int i = 0; i < W; ++i)
      for (int j = 0; j < H; ++j)
        const_cast<int&>(static_cast<std::array<int, H> const&>(static_cast<std::array<std::array<int, H>, W> const&>(data)[i])[j]) = 10 + j;
  }
};

Live Demo

Edit:

В отличие от некоторых людей использование const_cast таким образом не подразумевает поведение undefined. Фактически, как предложено в предложениях по релаксации constexpr, пользователям требуется выполнить эту работу с помощью const_cast, чтобы вызвать правильную перегрузку оператора индекса, по крайней мере, до тех пор, пока проблема не будет решена в С++ 17 (см. Ссылку).

Ответ 3

В то время как моя первая мысль была "зачем вам нужен метод constexpr для неконстантного массива"?...

Затем я сел и написал небольшой тест, чтобы понять, имеет ли смысл смысл:

#include <iostream>

using namespace std;
struct X{

    constexpr X()
    : _p { 0, 1, 2, 3, 4, 5, 6, 7, 9 }
    {
    }

    constexpr int& operator[](size_t i)
    {
        return _p[i];
    }

    int _p[10];
};

constexpr int foo()
{
    X x;
    x[3] = 4;
    return x[3];
}


auto main() -> int
{
    cout << foo() << endl;

    return 0;
}

Оказывается, что он делает.

Итак, я делаю вывод, что комитет принял то же "очевидное" мнение, что и сделал, и отказался от этой идеи.

Мне кажется, что предложение может быть представлено комитету для изменения его в С++ 17 - в качестве примера можно привести этот вопрос.

Ответ 4

Этот вопрос настолько заинтриговал меня, что я решил найти решение, которое позволило бы инициализировать массив во время компиляции с помощью функции, которая принимала значения x и y в качестве параметров.

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

#include <iostream>
#include <utility>


// function object that turns x and y into some output value. this is the primary predicate
struct init_cell_xy
{
    constexpr init_cell_xy() = default;

    constexpr int operator()(int x, int y) const
    {
        return (1 + x) * (1 + y);
    }
};

// function object that applies y to a given x
template<int X = 1>
struct init_cell_for_x
{
    constexpr init_cell_for_x() = default;

    constexpr int operator()(int y) const
    {
        return _xy(X, y);
    }

private:
    init_cell_xy _xy;
};

// an array of dimension 1, initialised at compile time
template<int Extent>
struct array1
{
    template<class F, int...Is>
    constexpr array1(F&& f, std::integer_sequence<int, Is...>)
    : _values { f(Is)... }
    {}

    template<class F>
    constexpr array1(F&& f = init_cell_for_x<>())
    : array1(std::forward<F>(f), std::make_integer_sequence<int, Extent>())
    {}

    constexpr auto begin() const { return std::begin(_values); }
    constexpr auto end() const { return std::end(_values); }
    constexpr auto& operator[](size_t i) const {
        return _values[i];
    }

private:
    int _values[Extent];

    friend std::ostream& operator<<(std::ostream& os, const array1& s)
    {
        os << "[";
        auto sep = " ";
        for (const auto& i : s) {
            os << sep << i;
            sep = ", ";
        }
        return os << " ]";
    }
};

// an array of dimension 2 - initialised at compile time
template<int XExtent, int YExtent>
struct array2
{
    template<int...Is>
    constexpr array2(std::integer_sequence<int, Is...>)
    : _xs { array1<YExtent>(init_cell_for_x<Is>())... }
    {}

    constexpr array2()
    : array2(std::make_integer_sequence<int, XExtent>())
    {}

    constexpr auto begin() const { return std::begin(_xs); }
    constexpr auto end() const { return std::end(_xs); }
    constexpr auto& operator[](size_t i) const {
        return _xs[i];
    }

private:
    array1<YExtent> _xs[XExtent];

    friend std::ostream& operator<<(std::ostream& os, const array2& s)
    {
        os << "[";
        auto sep = " ";
        for (const auto& i : s) {
            os << sep << i;
            sep = ",\n  ";
        }
        return os << " ]";
    }

};




auto main() -> int
{
    using namespace std;

    constexpr array2<6,6> a;

    cout << a << endl;
    return 0;
}