How to solve the issue of "read of non-constexpr variable 'a' is not allowed in a constant expression" with boost.hana
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;
}