4

我有这样的代码:

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添加到函数参数,但它同样不起作用。理论上,所有这些函数都可以在编译时计算。但是我的知识找不到问题在哪里。

4

3 回答 3

5

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_header函数constexpr在运行时无法使用¹,这不符合其在编译时和运行时都可用的要求。

修复相当简单:使用适用于两种情况的代码:

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

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


¹“但它也不起作用consteval!” - 诚然,答案中给出的推理对于 是足够的constexpr,但我忽略了更根本的原因,因为它在这里不相关。

于 2021-11-18T11:36:50.797 回答
3

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

图片

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

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

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

它确实意味着“被初始化”,但更重要的是关于在被评估的表达式的上下文中先前初始化的可见性。在您的示例中,在评估 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...)不是核心常量表达式,因为它的调用参数来自函数的参数,而 args 也不是核心常量表达式。

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

我希望也可能对您有所帮助。

于 2021-11-18T11:34:08.487 回答
0

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

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 并且在编译时知道)

于 2021-11-18T12:09:46.337 回答