如何用 boost.hana 解决 "read of non-constexpr variable 'a' is not allowed in a constant expression" 的问题
How to solve the issue of "read of non-constexpr variable 'a' is not allowed in a constant expression" with boost.hana
我正在使用带有 Boost.hana 的 c++17 来编写一些元编程程序。一个困扰我的问题是在像 static_assert 这样的 constexpr 上下文中可以使用什么样的表达式。这是一个例子:
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() {
return data;
}
};
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2.1
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
// static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
}
{ //test2.2
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
}
首先我写了一个 class X 字段 data 和一个访问器 getData() 。在main()的test1部分,x1.data和x1.getData() 的行为与我预期的一样。但是在 test2 部分,将参数更改为 boost::hana 的元组,static_assert(x2.data[0_c] == 1_c)
仍然表现良好但 static_assert(x2.getData()[0_c] == 1_c)
编译失败,错误为'在常量表达式 中不允许读取非 constexpr 变量 'x2''。奇怪的是,如果我将 x2.getData()[0_c]
拆分为 auto data = x2.getData();
和 static_assert(data[0_c] == 1_c);
,它又可以正常编译了。我希望他们的行为相同。那么谁能帮忙解释一下为什么在这个例子中static_assert不能使用x2.getData()[0_c]
?
重现:clang++8.0 -I/path/to/hana-1.5.0/include -std=c++17 Test.cpp
问题是因为您正在尝试检索 运行 时间值并在编译时对其进行测试。
你可以做的是在编译时通过 decltype
强制表达式,它会像一个魅力:)。
static_assert(decltype(x2.getData()[0_c]){} == 1_c);
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() {
return data;
}
};
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
static_assert(decltype(x2.getData()[0_c]){} == 1_c);
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
}
现在表达式在编译时求值,因此类型在编译时已知,并且由于它在计算时也可构造,因此可以在 static_assert
中使用它
所以首先,您在 getData()
方法中缺少 const 限定符,所以它应该是:
constexpr T getData() const
如果变量未标记为 constexpr,至少从标准的角度来看,没有变量被提升为 constexpr。
请注意,x1
没有必要,因为 X
类型专门用于 hana::integral_constant,因为 1_c
的结果是没有用户定义的复制构造函数的类型,内部不包含任何数据,所以 getData()
中的复制操作实际上是空操作,所以表达式:
static_assert(x1.getData() == 1_c);
很好,因为没有完成实际的复制(也不需要访问 x1
的非 const this
指针)。
这与您的 hana::tuple
容器非常不同,它包含 x2.data
字段中数据的 hana::tuple
的实际复制构造。这需要实际访问您的 this
指针 - 在 x1
的情况下这不是必需的,它也不是 constexpr 变量。
这意味着您在 x1
和 x2
中都表达了错误的意图,并且至少对于 x2
,有必要将这些变量标记为 constexpr。还要注意,使用空元组,它是一般 hana::tuple
的基本空的(没有用户定义的复制构造函数)专业化,确实可以无缝工作(test3 部分):
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() const {
return data;
}
};
template<typename V>
constexpr auto make_X(V value)
{
return value;
}
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2
constexpr auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
{ //test3
auto x3 = X(boost::hana::make_tuple());
static_assert(x3.data == boost::hana::make_tuple());
static_assert(x3.getData() == boost::hana::make_tuple());
}
}
问题是 boost::hana::tuple
没有复制构造函数。
它有 a constructor 看起来 像复制构造函数:
template <typename ...dummy, typename = typename std::enable_if<
detail::fast_and<BOOST_HANA_TT_IS_CONSTRUCTIBLE(Xn, Xn const&, dummy...)...>::value
>::type>
constexpr tuple(tuple const& other)
: tuple(detail::from_index_sequence_t{},
std::make_index_sequence<sizeof...(Xn)>{},
other.storage_)
{ }
但由于这是一个模板,所以它是 not a copy constructor。
因为boost::hana::tuple
没有复制构造函数,一个是declared implicitly并且定义为默认(它没有被抑制因为boost::hana::tuple
没有any 复制或移动构造函数或赋值运算符,因为,您猜对了,它们不能是模板)。
这里我们看到 implementation divergence,在以下程序的行为中得到证明:
struct A {
struct B {} b;
constexpr A() {};
// constexpr A(A const& a) : b{a.b} {} // #1
};
int main() {
auto a = A{};
constexpr int i = (A{a}, 0);
}
gcc 接受,而 Clang 和 MSVC 拒绝,但如果第 #1
行未注释则接受。也就是说,编译器不同意非(直接)空 class 的隐式定义的复制构造函数是否允许在常量评估上下文中使用。
根据 Clang 11 的 implicitly-defined copy constructor there is no way that #1 is any different to constexpr A(A const&) = default;
so gcc is correct. Note also that if we give B a user-defined constexpr copy constructor Clang and MSVC again accept, so the issue appears to be that these compilers are unable to track the constexpr copy constructibility of recursively empty implicitly copyable classes. Filed bugs for MSVC and Clang (fixed 定义。
请注意 operator[]
的使用是转移注意力;问题是编译器是否允许在 static_assert
.
等常量求值上下文中调用 getData()
(复制构造 T
)
显然,理想的解决方案是 Boost.Hana 更正 boost::hana::tuple
,使其具有实际的 copy/move 构造函数和 copy/move 赋值运算符。 (这将修复您的用例,因为代码将调用用户提供的复制构造函数,这在常量评估上下文中是允许的。)As a workaround,您可以考虑黑客攻击 getData()
来检测非-有状态 T
:
constexpr T getData() {
if (data == T{})
return T{};
else
return data;
}
我正在使用带有 Boost.hana 的 c++17 来编写一些元编程程序。一个困扰我的问题是在像 static_assert 这样的 constexpr 上下文中可以使用什么样的表达式。这是一个例子:
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() {
return data;
}
};
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2.1
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
// static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
}
{ //test2.2
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
}
首先我写了一个 class X 字段 data 和一个访问器 getData() 。在main()的test1部分,x1.data和x1.getData() 的行为与我预期的一样。但是在 test2 部分,将参数更改为 boost::hana 的元组,static_assert(x2.data[0_c] == 1_c)
仍然表现良好但 static_assert(x2.getData()[0_c] == 1_c)
编译失败,错误为'在常量表达式 中不允许读取非 constexpr 变量 'x2''。奇怪的是,如果我将 x2.getData()[0_c]
拆分为 auto data = x2.getData();
和 static_assert(data[0_c] == 1_c);
,它又可以正常编译了。我希望他们的行为相同。那么谁能帮忙解释一下为什么在这个例子中static_assert不能使用x2.getData()[0_c]
?
重现:clang++8.0 -I/path/to/hana-1.5.0/include -std=c++17 Test.cpp
问题是因为您正在尝试检索 运行 时间值并在编译时对其进行测试。
你可以做的是在编译时通过 decltype
强制表达式,它会像一个魅力:)。
static_assert(decltype(x2.getData()[0_c]){} == 1_c);
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() {
return data;
}
};
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2
auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
static_assert(decltype(x2.getData()[0_c]){} == 1_c);
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
}
现在表达式在编译时求值,因此类型在编译时已知,并且由于它在计算时也可构造,因此可以在 static_assert
中使用它所以首先,您在 getData()
方法中缺少 const 限定符,所以它应该是:
constexpr T getData() const
如果变量未标记为 constexpr,至少从标准的角度来看,没有变量被提升为 constexpr。
请注意,x1
没有必要,因为 X
类型专门用于 hana::integral_constant,因为 1_c
的结果是没有用户定义的复制构造函数的类型,内部不包含任何数据,所以 getData()
中的复制操作实际上是空操作,所以表达式:
static_assert(x1.getData() == 1_c);
很好,因为没有完成实际的复制(也不需要访问 x1
的非 const this
指针)。
这与您的 hana::tuple
容器非常不同,它包含 x2.data
字段中数据的 hana::tuple
的实际复制构造。这需要实际访问您的 this
指针 - 在 x1
的情况下这不是必需的,它也不是 constexpr 变量。
这意味着您在 x1
和 x2
中都表达了错误的意图,并且至少对于 x2
,有必要将这些变量标记为 constexpr。还要注意,使用空元组,它是一般 hana::tuple
的基本空的(没有用户定义的复制构造函数)专业化,确实可以无缝工作(test3 部分):
#include <boost/hana.hpp>
using namespace boost::hana::literals;
template <typename T>
class X {
public:
T data;
constexpr explicit X(T x) : data(x) {}
constexpr T getData() const {
return data;
}
};
template<typename V>
constexpr auto make_X(V value)
{
return value;
}
int main() {
{ // test1
auto x1 = X(1_c);
static_assert(x1.data == 1_c);
static_assert(x1.getData() == 1_c);
}
{ //test2
constexpr auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
static_assert(x2.data[0_c] == 1_c);
static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
auto data = x2.getData();
static_assert(data[0_c] == 1_c);
}
{ //test3
auto x3 = X(boost::hana::make_tuple());
static_assert(x3.data == boost::hana::make_tuple());
static_assert(x3.getData() == boost::hana::make_tuple());
}
}
问题是 boost::hana::tuple
没有复制构造函数。
它有 a constructor 看起来 像复制构造函数:
template <typename ...dummy, typename = typename std::enable_if<
detail::fast_and<BOOST_HANA_TT_IS_CONSTRUCTIBLE(Xn, Xn const&, dummy...)...>::value
>::type>
constexpr tuple(tuple const& other)
: tuple(detail::from_index_sequence_t{},
std::make_index_sequence<sizeof...(Xn)>{},
other.storage_)
{ }
但由于这是一个模板,所以它是 not a copy constructor。
因为boost::hana::tuple
没有复制构造函数,一个是declared implicitly并且定义为默认(它没有被抑制因为boost::hana::tuple
没有any 复制或移动构造函数或赋值运算符,因为,您猜对了,它们不能是模板)。
这里我们看到 implementation divergence,在以下程序的行为中得到证明:
struct A {
struct B {} b;
constexpr A() {};
// constexpr A(A const& a) : b{a.b} {} // #1
};
int main() {
auto a = A{};
constexpr int i = (A{a}, 0);
}
gcc 接受,而 Clang 和 MSVC 拒绝,但如果第 #1
行未注释则接受。也就是说,编译器不同意非(直接)空 class 的隐式定义的复制构造函数是否允许在常量评估上下文中使用。
根据 Clang 11 的 implicitly-defined copy constructor there is no way that #1 is any different to constexpr A(A const&) = default;
so gcc is correct. Note also that if we give B a user-defined constexpr copy constructor Clang and MSVC again accept, so the issue appears to be that these compilers are unable to track the constexpr copy constructibility of recursively empty implicitly copyable classes. Filed bugs for MSVC and Clang (fixed 定义。
请注意 operator[]
的使用是转移注意力;问题是编译器是否允许在 static_assert
.
getData()
(复制构造 T
)
显然,理想的解决方案是 Boost.Hana 更正 boost::hana::tuple
,使其具有实际的 copy/move 构造函数和 copy/move 赋值运算符。 (这将修复您的用例,因为代码将调用用户提供的复制构造函数,这在常量评估上下文中是允许的。)As a workaround,您可以考虑黑客攻击 getData()
来检测非-有状态 T
:
constexpr T getData() {
if (data == T{})
return T{};
else
return data;
}