¿Por qué no es const std::array:: operator[] constexpr?


Estoy tratando de llenar una matriz 2D en tiempo de compilación con una función dada. Aquí está mi código:

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

Funciona bien, table.data se rellena correctamente en tiempo de compilación.

Sin embargo, si cambio matriz 2D simple int[H][W] con std::array<std::array<int, H>, W>, tengo un error en el cuerpo del bucle:

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

Obviamente, estoy tratando de llamar sobrecarga no constante de std::array::operator[], que no es constexpr. La pregunta es, ¿por qué no es constexpr? Si C++14 nos permite modificar variables declaradas en el ámbito constexpr, por qué esto es ¿no es compatible con std::array?

Solía pensar que std::array es como una matriz simple, solo que mejor. Pero aquí hay un ejemplo, donde puedo usar matriz simple, pero no puedo usar std::array.

Author: Mikhail, 2015-12-10

4 answers

Ok, es de hecho un descuido en el estándar. Incluso existe una propuesta para arreglar esto: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0107r0.pdf

[N3598] eliminó el marcado implícito de las funciones miembro constexpr como const. Sin embargo, el las funciones miembro de std::array no se revisaron después de este cambio, lo que llevó a una falta sorprendente de soporte para constexpr en la interfaz de std::array. Este documento corrige esta omisión agregando constexpr a la funciones miembro de std::array que pueden soportarlo con una cantidad mínima de trabajo.

 25
Author: Mikhail,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-10 13:04:21

std::array::operator[] dado que C++14 es constexpr pero también const calificado:

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

Así que tienes que lanzar los arrays para invocar la sobrecarga correcta 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;
  }
};

Demostración En Vivo

Editar:

A diferencia de algunas personas, el uso de const_cast de tal manera no implica un comportamiento indefinido. De hecho, como se propone en las propuestas para la relajación de constexpr, los usuarios deben hacer este trabajo con const_cast para evocar el operador de subíndice correcto sobrecarga al menos hasta que el problema se resuelva en C++17 (ver enlace).

 9
Author: 101010,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:17:39

Mientras que mi primer pensamiento fue "¿por qué necesitaría un método constexpr en una matriz no-const"? ...

Luego me senté y escribí una pequeña prueba para ver si la idea tenía sentido:

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

Resulta que sí.

Así que estoy sacando la conclusión de que el comité tomó el mismo punto de vista "obvio" que yo y descartó la idea.

Me parece que se podría presentar una propuesta al comité para cambiarla en c++17, dando esta pregunta como ejemplo.

 5
Author: Richard Hodges,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-10 11:52:54

Esta pregunta me intrigó tanto que decidí encontrar una solución que permitiera que la matriz se inicializara en tiempo de compilación con una función que tomara x e y como parámetros.

Presumiblemente esto podría ser adaptado para cualquier número de dimensiones.

#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
Author: Richard Hodges,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-10 14:03:32