问题描述
我正在使用带有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);
}
}
首先,我编写一个类 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)
编译失败,错误为' read of non-constexpr variable常量表达式中不允许使用'x2''.奇怪的是,如果将x2.getData()[0_c]
拆分为auto data = x2.getData();
和static_assert(data[0_c] == 1_c);
,它将再次编译正常.我希望它们的行为相同.那么谁能帮助解释为什么在本示例中x2.getData()[0_c]
不能在static_assert中使用?
要重现:clang ++ 8.0 -I/path/to/hana-1.5.0/include -std = c ++ 17 Test.cpp
问题是boost::hana::tuple
没有副本构造函数.
它具有 a构造函数 看起来就像一个复制构造函数:
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_)
{ }
但是,由于这是模板,因此它是不是复制构造函数.
由于boost::hana::tuple
没有副本构造函数,因此声明了隐式并定义为默认值(由于boost::hana::tuple
没有任何复制或移动构造函数或赋值运算符,因此不会被抑制,因为您猜到了它们不能是模板)./p>
在这里,我们看到实现差异,在以下程序的行为中得到了证明:
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
行,则接受.也就是说,编译器在是否允许在常量求值上下文中使用非(直接)空类的隐式定义的副本构造函数上存在分歧.
根据隐式定义的副本构造函数的定义 #1与constexpr A(A const&) = default;
没有任何区别,因此gcc是正确的.还要注意,如果我们给B一个用户定义的constexpr复制构造函数Clang和MSVC再次接受,那么问题似乎是这些编译器无法跟踪递归空隐式可复制类的constexpr复制可构造性. MSVC 的已提交错误a>和 C语(已修复,适用于Clang 11).
请注意,使用operator[]
是红色鲱鱼.问题在于,编译器是否允许在诸如static_assert
之类的常量求值上下文中调用getData()
(复制构造T
).
很显然,理想的解决方案是Boost.Hana纠正boost::hana::tuple
,使其具有实际的复制/移动构造函数和复制/移动赋值运算符. (这将解决您的用例,因为代码将调用用户提供的副本构造函数,在常量求值上下文中是允许的.)作为解决方法,您可以考虑黑客攻击getData()
来检测无状态T
的情况:
constexpr T getData() {
if (data == T{})
return T{};
else
return data;
}
I'm using c++17 with Boost.hana to write some meta-programming programs. One issue stuck me is what kind of expression can be used in a constexpr context like static_assert. Here is an example:
#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);
}
}
First I write a class X with a field data and an accessor getData(). In the main()'s test1 part, x1.data and x1.getData() behave same as I expected. But in the test2 part, changing the argument to a boost::hana's tuple, static_assert(x2.data[0_c] == 1_c)
still behaves fine but static_assert(x2.getData()[0_c] == 1_c)
fails compilation, with error of 'read of non-constexpr variable 'x2' is not allowed in a constant expression'. What weired is if I split x2.getData()[0_c]
into auto data = x2.getData();
and static_assert(data[0_c] == 1_c);
it compiles fine again. I'd expect they behave the same. So can anyone help explain why x2.getData()[0_c]
can not be used in static_assert in this example?
To reproduce: clang++8.0 -I/path/to/hana-1.5.0/include -std=c++17 Test.cpp
The problem is that boost::hana::tuple
does not have a copy constructor.
It has a constructor that looks like a copy 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_)
{ }
But since this is a template, it is not a copy constructor.
Since boost::hana::tuple
does not have a copy constructor, one is declared implicitly and defined as defaulted (it is not suppressed since boost::hana::tuple
does not have any copy or move constructors or assignment operators, because, you guessed it, they cannot be templates).
Here we see implementation divergence, demonstrated in the behavior of the following program:
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 accepts, while Clang and MSVC reject, but accept if line #1
is uncommented. That is, the compilers disagree on whether the implicitly-defined copy constructor of a non-(directly-)empty class is permissible to use within constant-evaluation context.
Per the definition of the 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 for Clang 11).
Note that the use of operator[]
is a red herring; the issue is whether the compilers allow the call to getData()
(which copy-constructs T
) within a constant-evaluation context such as static_assert
.
Obviously, the ideal solution would be for Boost.Hana to correct boost::hana::tuple
such that it has actual copy/move constructors and copy/move assignment operators. (This would fix your use case since the code would be calling user-provided copy constructors, which are permissible in constant-evaluation context.) As a workaround, you could consider hacking getData()
to detect the case of non-stateful T
:
constexpr T getData() {
if (data == T{})
return T{};
else
return data;
}
这篇关于如何解决“在常量表达式中不允许读取非constexpr变量'a'的问题".与boost.hana的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!