为什么非 const std::array::operator[] 不是 constexpr?
Why is non-const std::array::operator[] not constexpr?
我正在尝试使用给定函数在编译时填充二维数组。这是我的代码:
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
在编译时正确填充。
但是,如果我将普通二维数组 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
呢?如果 C++14 允许我们修改在 constexpr
范围内声明的变量,为什么 std::array
不支持?
我曾经认为 std::array
就像普通数组一样,只是更好。但这里有一个例子,我可以使用普通数组,但不能使用 std::array
.
虽然我的第一个想法是 "why would you need a constexpr method on a non-const array"? ...
然后我坐下来写了一个小测试,看看这个想法是否有意义:
#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;
}
事实证明确实如此。
所以我得出的结论是,委员会采取了与我相同的 "obvious" 观点并否决了这个想法。
在我看来,好像可以向委员会提出一项提案以在 c++17 中对其进行更改 - 以这个问题为例。
std::array::operator[]
因为 C++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;
}
};
编辑:
与某些人相反,以这种方式使用 const_cast
并不意味着未定义的行为。事实上,正如在放宽 constexpr
的提案中所提议的那样,用户需要使用 const_cast
来解决此问题,以便至少在问题解决之前引发正确的下标运算符重载在 C++17 中 ().
好吧,确实是标准疏忽了。甚至存在解决此问题的建议: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:已在 C++17 中修复:https://en.cppreference.com/w/cpp/container/array/operator_at
这个问题让我很感兴趣,所以我决定找出一个解决方案,允许在编译时使用一个以 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;
}
我正在尝试使用给定函数在编译时填充二维数组。这是我的代码:
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
在编译时正确填充。
但是,如果我将普通二维数组 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
呢?如果 C++14 允许我们修改在 constexpr
范围内声明的变量,为什么 std::array
不支持?
我曾经认为 std::array
就像普通数组一样,只是更好。但这里有一个例子,我可以使用普通数组,但不能使用 std::array
.
虽然我的第一个想法是 "why would you need a constexpr method on a non-const array"? ...
然后我坐下来写了一个小测试,看看这个想法是否有意义:
#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;
}
事实证明确实如此。
所以我得出的结论是,委员会采取了与我相同的 "obvious" 观点并否决了这个想法。
在我看来,好像可以向委员会提出一项提案以在 c++17 中对其进行更改 - 以这个问题为例。
std::array::operator[]
因为 C++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;
}
};
编辑:
与某些人相反,以这种方式使用 const_cast
并不意味着未定义的行为。事实上,正如在放宽 constexpr
的提案中所提议的那样,用户需要使用 const_cast
来解决此问题,以便至少在问题解决之前引发正确的下标运算符重载在 C++17 中 (
好吧,确实是标准疏忽了。甚至存在解决此问题的建议: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:已在 C++17 中修复:https://en.cppreference.com/w/cpp/container/array/operator_at
这个问题让我很感兴趣,所以我决定找出一个解决方案,允许在编译时使用一个以 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;
}