How to solve the issue of “read of non-constexpr variable 'a' is not allowed in a constant expression” with boost.hana

旧巷老猫 提交于 2020-03-18 11:44:28

问题


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


回答1:


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;
}



回答2:


The issue is because you are trying to retrieve a run time value and test it at compilation.

What you can do is to force the expression at compile time through a decltype and it will work like a charm :).

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);
    }
}

Now the expression is evaluated at compile time, so the type is known at compile time and since it is constructible at compute time also, it is possible to use it within a static_assert




回答3:


So first of all, you're missing const qualifier in getData() method, so it should be:

constexpr T getData() const

No variable is promoted, at least from standard point of view, to be constexpr if its not marked as constexpr.

Note that this is not necessary for x1 which is of type X specialized with hana::integral_constant, since result of 1_c is a type without user defined copy constructor, which does not contain any data internally, so a copy operation in getData() is in fact a no-op, so expression: static_assert(x1.getData() == 1_c); is fine, since there is no actual copy done (nor access to non-const this pointer of x1 is necessary).

This is very different for your container with hana::tuple which contains factual copy construction of hana::tuple from data in x2.data field. This requires factual access to your this pointer - which wasn't necessary in case of x1, which also was not a constexpr variable.

This means that you are expressing your intent wrong with both x1 and x2, and it is necessary, at least for x2, to mark these variables as constexpr. Note also, that using empty tuple, which is an basically empty (no user defined copy constructors) specialization of general hana::tuple, does work seamlessly (test3 section):

#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());
    }
}


来源:https://stackoverflow.com/questions/60387132/how-to-solve-the-issue-of-read-of-non-constexpr-variable-a-is-not-allowed-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!