【问题标题】:Compiler can't execute constexpr expression编译器无法执行 constexpr 表达式
【发布时间】:2021-12-29 06:19:28
【问题描述】:

我有这样的代码:

template<typename ... Args>
constexpr size_t get_init_size(Args ... args) {
    return sizeof...(Args);
}

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    constexpr size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

constexpr auto create_ipv4_header() {
    constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
    return x;
}

我知道这是虚拟代码,但我将其隔离以查找错误。

编译器给我错误(GCC):

In instantiation of 'constexpr auto make_generic_header(Args&& ...) [with Args = {int, int, int}]':
/tmp/tmp.CaO5YHcqd8/network.h:39:43:   required from here
/tmp/tmp.CaO5YHcqd8/network.h:31:22: error: 'args#0' is not a constant expression
   31 |     constexpr size_t header_lenght = get_init_size(args...);
      |                      ^~~~~~~~~~~~~

我尝试将限定符 const 添加到函数参数,但它同样不起作用。理论上,所有这些函数都可以在编译时计算。但是我的知识找不到问题在哪里。

【问题讨论】:

  • 我想你可以在stackoverflow.com/questions/31714790/…找到答案
  • @user8510613 如果你说这是参考问题,我对简单变量有同样的错误,我现在用文本修复它,与 const cv + 简单变量相同
  • @emik_g 删除引用并不能解决问题,因为它仍然是“引用变量的 id 表达式”。您可以直接通过return get_init_size(args...) 解决此问题并完全删除header_length
  • 问题是 args 在上下文 constexpr 函数 make_generic_header 中仍将被视为运行时初始化程序。它可以使用运行时值调用,因此涉及它们的扩展不能是 make_generic_headers 正文的上下文中的 constexpr
  • @Tharsalys 正如我在隔离此代码之前所说的那样,在实际代码中,我将使用此变量作为模板中参数的一部分,例如 something&lt;header_length&gt;

标签: c++ templates c++17 constexpr constexpr-function


【解决方案1】:

constexpr 对变量和函数的含义不同。

对于变量,这意味着变量必须是编译时的。所以需要用常量表达式初始化。

对于函数,constexpr 表示该函数可以在编译时运行,也可以在运行时使用内部相同的代码运行。因此,您在内部执行的所有操作也必须适用于运行时调用。

考虑到这一点,让我们研究一下make_generic_header

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    constexpr size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

这里,header_lenght 是一个constexpr 变量,所以必须是编译时的。因此,get_init_size(args...) 也必须在编译时完成。但是,由于各种原因,参数不是常量表达式,所以这是行不通的。如果这确实有效,则意味着make_generic_headerconstexpr 函数)在运行时不可用¹,这不符合其在编译时和运行时都可用的要求。

解决方法相当简单:使用适用于两种情况的代码:

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

你发现了吗?唯一的变化是从header_lenght 中删除constexpr。如果这个函数在编译时运行,它仍然可以工作,整个函数调用表达式将是一个常量表达式。


¹“但它在 consteval 中也不起作用!” - 没错,答案中给出的推理对于constexpr 来说已经足够了,但我忽略了更根本的原因,因为它与这里无关。

【讨论】:

  • 如果变量不是 constexpr,您能否阐明编译器如何使其在编译时运行?我的想法是,参数仅在调用站点“在编译时已知”,因此如果函数本身正在调用另一个函数(如本例中的make_generic_header()),则下一个函数的参数不是“constexpr”,因为这样的。
  • 感谢您的解释,正如我在其他答案评论中所说的,我使用header_lenght 作为模板的参数,它必须在编译时知道......
  • @Tharsalys,这并不是它通常的实际工作方式,但您可以将编译时调用 constexpr 函数视为编译器编译包含该函数调用的单独可执行文件以及函数的代码及其所有依赖项,然后在编译过程中运行该可执行文件。在您第一次跳转到constexpr 土地的最外层和里面的所有嵌套代码之间存在很大的分歧。进入后,常规运行时代码或多或少以相同的方式工作,但在编译期间运行。
  • @chris 所以如果不是过于简化的话,说你只需要在最外层使用constexpr 对吗,因为一旦进入内部,这只是一个术语问题,因为上下文已经确定了吗?
  • @Tharsalys,是的,这听起来很准确。您调用的函数本身也必须是constexpr,但鉴于此,它是叶函数的常规代码。当编译器遇到外部调用时,它必须切换齿轮并启动其持续评估。如果更简单,一种更常见的思考方式是,最外层的调用启动一个在编译器中实现的 C++ 解释器,然后在该特殊环境中的调用中运行 C++ 代码。完成后,解释器会产生最终结果。 (真正的编译器基于这两种方法。)
【解决方案2】:

这不是参考问题。而constexpr 变量和constexpr 函数是不同的东西。引用自回答https://stackoverflow.com/a/31720324

但是,从 i 的角度来看,该引用没有预先初始化:它是一个参数。一旦调用 ByReference,它就会被初始化。

这很好,因为 f 确实有前面的初始化。 f 的初始化器也是一个常量表达式,因为在这种情况下隐式声明的默认构造函数是 constexpr(第 12.1/5 节)。 因此 i 由一个常量表达式初始化,而调用本身就是一个常量表达式。

关于“之前的初始化”,引用自this

它确实意味着“被初始化”,但更重要的是关于在被评估的表达式的上下文中先前初始化的可见性。在您的示例中,在评估 func(0) 的上下文中,编译器具有看到 rf 初始化为 0 的上下文。但是,在仅评估 func 中的表达式 rf 的上下文中,它看不到 rf 的初始化.分析是本地的,因为它不会分析每个呼叫站点。这导致 func 中的表达式 rf 本身不是常量表达式,而 func(0) 是常量表达式。

对应于你的情况,该行:

constexpr size_t header_length = get_init_size(args...);

header_length的角度来看,get_init_size(args...)不是核心常量表达式,因为它的invoke参数来自函数的参数,而args也不是核心常量表达式。

简单的修改就可以让你的代码工作,你可以去掉header_length中的constexpr限定符,或者直接在make_generic_header的函数体中返回get_init_size(args...);

我希望thisthis 也能帮助到你。

【讨论】:

  • 如果我将删除 constexpr 限定符代码将在运行时执行?现在这个函数没有任何运行时代码(它只是传递可以运行时的参数,但不使用它)。并感谢您的参考,它真的很感兴趣!
  • @emik_g 删除header_length上的constexpr限定符并不代表上面的代码会产生一些运行时执行,可以查看godbolt.org/z/zq8h4bq6P,直接得到3。
  • @emik_g en.cppreference.com/w/cpp/language/constexpr constexpr 变量和 constexpr 函数是不同的东西
【解决方案3】:

我重写了可以工作的代码,我认为会在编译时执行:

template<typename ... Args>
constexpr auto make_generic_header(const Args ... args) {
    std::integral_constant<size_t, sizeof...(Args)> header_lenght;
    return header_lenght.value;
}

constexpr auto create_ipv4_header() {
    constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
    return x;
}

我删除了函数 get_init_size 并使用了模板参数的代码部分(保证它将在编译时执行)并返回传递给函数的参数数量(对于所有对象值的 std::integral_constant相同并且知道编译时间)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多