首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何用boost.hana解决“常数表达式中不允许读取非常数变量'a‘”的问题

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

Stack Overflow用户
提问于 2020-02-25 03:33:36
回答 3查看 1.1K关注 0票数 10

我正在使用c++17和Boost.hana编写一些元编程程序。困扰我的一个问题是,在像static_assert这样的constexpr上下文中可以使用什么样的表达式。下面是一个示例:

代码语言:javascript
复制
#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,static_assert(x2.data[0_c] == 1_c)仍然运行良好,但static_assert(x2.getData()[0_c] == 1_c)编译失败,在常量表达式中不允许错误“读取非参数变量'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/包括-std=c++17 Test.cpp

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-03-02 17:57:20

问题是boost::hana::tuple没有副本构造函数。

它有一个看起来像副本构造函数的构造者

代码语言:javascript
复制
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没有任何复制或移动构造函数或赋值运算符,因此不会被抑制,因为您猜到了,它们不能是模板)。

在这里,我们看到了执行分歧,在以下程序的行为中演示了这一点:

代码语言:javascript
复制
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嘎吱声 ( Clang 11的固定)注册了bug。

请注意,operator[]的使用是一条红线;问题是编译器是否允许在一个常量计算上下文(如static_assert )中调用getData() (复制-构造T)。

显然,理想的解决方案是Boost.Hana更正boost::hana::tuple,使其具有实际的复制/移动构造函数和复制/移动赋值运算符。(这将修复您的用例,因为代码将调用用户提供的副本构造函数,这在常量评估上下文中是允许的。) 作为一种解决办法,您可以考虑对getData()进行黑客攻击,以检测非有状态T的情况。

代码语言:javascript
复制
constexpr T getData() {
    if (data == T{})
        return T{};
    else
        return data;
}
票数 6
EN

Stack Overflow用户

发布于 2020-02-28 10:42:31

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

您可以做的是在编译时强制表达式通过一个decltype,它将像魅力一样工作:)。

static_assert(decltype(x2.getData()[0_c]){} == 1_c);

代码语言:javascript
复制
#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中使用它。

票数 1
EN

Stack Overflow用户

发布于 2020-02-28 15:31:11

因此,首先,您在getData()方法中缺少了const限定符,所以应该是:

代码语言:javascript
复制
constexpr T getData() const

至少从标准的角度来看,如果变量没有标记为constexpr,则没有任何变量被提升为警察。

注意,对于x1来说,这是不必要的,因为1_c的结果是一种没有用户定义的复制构造函数的类型,该构造函数内部不包含任何数据,因此getData()中的复制操作实际上是无操作的,所以表达式:static_assert(x1.getData() == 1_c);很好,因为没有实际的复制完成(也没有必要访问x1的非const this指针)。

对于使用hana::tuple的容器来说,这是非常不同的,它包含来自x2.data字段中数据的hana::tuple的实际复制构造。这需要对您的this指针进行实际访问--对于x1来说,这是不必要的,而后者也不是一个constexpr变量。

这意味着您对x1x2都表示了错误的意图,而且至少对于x2来说,必须将这些变量标记为constexpr。还请注意,使用空元组(基本上是通用hana::tuple的空(没有用户定义的复制构造函数)专门化)可以无缝地工作(test3部分):

代码语言:javascript
复制
#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());
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60387132

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档