Frage Warum ist nicht-const std :: array :: operator [] nicht constexpr?


Ich versuche, ein 2D-Array zur Kompilierzeit mit einer bestimmten Funktion zu füllen. Hier ist mein Code:

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

Es funktioniert gut, table.data wird zur Kompilierzeit richtig ausgefüllt.

Wenn ich jedoch ein einfaches 2D-Array ändere int[H][W] mit std::array<std::array<int, H>, W>Ich habe einen Fehler im Schleifenkörper:

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

Offensichtlich versuche ich eine nicht-konstante Überladung zu nennen std::array::operator[]was nicht ist constexpr. Die Frage ist, warum es nicht ist constexpr? Wenn C ++ 14 uns erlaubt, in deklarierte Variablen zu ändern constexpr Umfang, warum dies nicht unterstützt wird std::array?

Früher habe ich das gedacht std::array ist wie einfache Array, nur besser. Aber hier ist ein Beispiel, wo ich einfache Array verwenden kann, aber nicht verwenden kann std::array.


28
2017-12-10 10:42


Ursprung


Antworten:


Ok, es ist in der Tat ein Versehen im Standard. Es gibt sogar einen Vorschlag, dies zu beheben: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0107r0.pdf

[N3598] entfernte die implizite Kennzeichnung von conexpr-Elementfunktionen als const. Aber die   Die Member-Funktionen von std :: array wurden nach dieser Änderung nicht erneut aufgerufen, was zu einem überraschenden Mangel führte   Unterstützung von consExpr in der Schnittstelle von std :: array. Dieses Papier behebt dieses Versäumnis durch Hinzufügen   constexpr zu den Member-Funktionen von std :: array, die es mit einer minimalen Menge an unterstützen können   Arbeit.


25
2017-12-10 11:53



std::array::operator[] seit C ++ 14 ist constexpr aber ist es auch const qualifiziert:

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

Daher müssen Sie die Arrays so umwandeln, dass sie das richtige aufrufen operator[] Überlast:

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

Bearbeiten:

Im Gegensatz zu einigen Menschen, Verwendung von const_cast auf diese Weise bedeutet nicht undefiniertes Verhalten. In der Tat, wie in den Vorschlägen zur Entspannung vorgeschlagen constexprEs wird von den Benutzern benötigt, um dies zu umgehen const_cast um die richtige Subskriptoperatorüberladung auszulösen, zumindest bis das Problem in C ++ 17 gelöst ist (Siehe Link).


9
2017-12-10 11:40



Während mein erster Gedanke war, "warum würden Sie eine consExpr-Methode auf einem nicht-const-Array benötigen"? ...

Ich setzte mich dann hin und schrieb einen kleinen Test, um zu sehen, ob die Idee einen Sinn ergab:

#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;
}

Es stellt sich heraus, dass es so ist.

Ich ziehe also die Schlussfolgerung, dass das Komitee die gleiche "offensichtliche" Ansicht vertrat, die ich gemacht hatte, und die Idee abwertete.

Sieht für mich so aus, als ob ein Vorschlag an den Ausschuss geschickt werden könnte, um ihn in c ++ 17 zu ändern - und diese Frage als Beispiel zu nennen.


5
2017-12-10 11:40



Diese Frage faszinierte mich so sehr, dass ich beschloss, eine Lösung zu finden, mit der das Array zur Kompilierungszeit mit einer Funktion initialisiert werden konnte, die x und y als Parameter verwendete.

Vermutlich könnte dies für beliebig viele Dimensionen angepasst werden.

#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;
}

3
2017-12-10 14:03