提问者:小点点

如何用boost. hana解决“常量表达式中不允许读取非constexpr变量'a'”的问题


我正在使用带有Boost.hana的c17来编写一些元编程程序。有一个问题困扰着我,那就是在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);
    }
}

首先,我编写了一个带有字段数据和访问器getData()的类X。在main()的test1部分中,x1.data和x1.getData()的行为与我预期的一样。但是在test2部分中,将参数更改为boost::hana的tuple,< code > static _ assert(x2 . data[0 _ c]= = 1 _ c)仍然表现良好,但是< code > static _ assert(x2 . get data()[0 _ c]= = 1 _ c)编译失败,出现错误“在常量表达式中不允许读取非constexpr变量' x2 '”。如果我将< code>x2.getData()[0_c]拆分为< code > auto data = x2 . get data();和< code > static _ assert(data[0 _ c]= = 1 _ c);它再次编译正常。我希望他们的行为是一样的。所以有没有人能帮忙解释一下为什么这个例子中< code>x2.getData()[0_c]不能用在static_assert中?

重现:clang 8.0 -I/path/to/hana-1.5.0/include -std=c 17 Test.cpp


共3个答案

匿名用户

问题是boost::hana::tuple没有复制构造函数。

它有一个看起来像复制构造函数的构造函数:

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_)
{ }

但既然这是模板,就不是复制构造函数。

由于::hana::元组没有复制构造函数,因此隐式声明了一个并定义为默认值(它没有被抑制,因为boop::hana::元组没有任何复制或移动构造函数或赋值操作符,因为,你猜对了,它们不可能是模板)。

在这里,我们看到了实现分歧,这在以下程序的行为中得到了证明:

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拒绝,但如果行< code>#1未被注释,则接受。也就是说,编译器对非(直接)空类的隐式定义的复制构造函数是否允许在常量求值上下文中使用意见不一。

根据隐式定义的复制构造函数的定义,#1 与 constexpr A(A const 没有任何不同)

请注意,使用运算符[]是在转移注意力;问题是编译器是否允许在常量评估上下文(如static_assert)中调用getData()(它复制构造T)。

显然,理想的解决方案是Boost.Hana更正Boost::Hana::tuple,使其具有实际的复制/移动构造函数和复制/移动赋值运算符。(这将修复您的用例,因为代码将调用用户提供的副本构造函数,这在常量求值上下文中是允许的。)作为一种变通方法,您可以考虑破解<code>getData()

constexpr T getData() {
    if (data == T{})
        return T{};
    else
        return data;
}

匿名用户

问题是因为您试图检索运行时值并在编译时对其进行测试。

你所能做的就是在编译时通过一个< code>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 不是必需的,x1 的类型是专门使用 hana::integral_constant,因为 1_c 的结果是一个没有用户定义的复制构造函数的类型,它内部不包含任何数据,所以 getData() 中的复制操作实际上是一个无操作,所以表达式:static_assert(x1.getData() == 1_c); 很好, 由于没有完成实际的复制(也不需要访问非常量,因此 x1 指针是必需的)。

这对于具有< code>hana::tuple的容器来说非常不同,该容器包含来自< code>x2.data字段中的数据的< code>hana::tuple的实际副本构造。这需要对< code>this指针的实际访问——这在< code>x1的情况下是不必要的,因为它也不是constexpr变量。

这意味着您用x1x2表达了错误的意图,并且有必要将这些变量标记为constexpr,至少对于x2是这样。还要注意的是,使用empty tuple,这是通用<code>hana::tuple</code>的一种基本上为空的(没有用户定义的复制构造函数)专门化,确实可以无缝工作(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());
    }
}