I'm trying to fill a 2D array on compile time with a given function. Here is my 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
It works just fine, table.data
is properly populated at compile time.
However, if I change plain 2D array int[H][W]
with std::array<std::array<int, H>, W>
, I have an error in the loop body:
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
Obviously, I'm trying to call non-const overload of std::array::operator[]
, which is not constexpr
. The question is, why it is not constexpr
? If C++14 allows us to modify variables declared in constexpr
scope, why this is not supported by std::array
?
I used to think that std::array
is just like plain array, only better. But here is an example, where I can use plain array, but cannot use std::array
.
Ok, it is indeed an oversight in the standard. There even exists a proposal to fix this: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0107r0.pdf
[N3598] removed the implicit marking of constexpr member functions as const. However, the member functions of std::array were not revisited after this change, leading to a surprising lack of support for constexpr in std::array’s interface. This paper fixes this omission by adding constexpr to the member functions of std::array that can support it with a minimal amount of work.
UPD: Fixed in C++17: https://en.cppreference.com/w/cpp/container/array/operator_at
std::array::operator[]
since C++14 is constexpr
but is also const
qualified:
constexpr const_reference operator[]( size_type pos ) const;
^^^^^
Thus you have to cast the arrays to invoke the correct operator[]
overload:
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:
As opposed by some people, use of const_cast
in such a way does not imply undefined behaviour. In fact as proposed in the proposals for the relaxation of constexpr
, it is required by the users to do this work around with const_cast
in order to evoke the correct subscript operator overload at least until the issue is resolved in C++17 (see link).
While my first thought was "why would you need a constexpr method on a non-const array"? ...
I then sat down and wrote a little test to see if the idea made sense:
#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;
}
It turns out that it does.
So I'm drawing the conclusion that the committee took the same "obvious" view that I did and discounted the idea.
Looks to me as if a proposal could be put forward to the committee to change it in c++17 - giving this question as an example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With