【问题标题】:Function is not usable as a 'constexpr' function函数不能用作“constexpr”函数
【发布时间】:2021-06-26 09:04:42
【问题描述】:

看看下面的代码

#include <type_traits>

template <typename T>
struct basic_type {
  using type = T;
};

consteval auto foo(auto p, auto x) noexcept {
  if constexpr (p(x)) {
    return 1;
  } else {
    return 0;
  }
}

int main() {
    // This compiles
    return foo(
        []<typename T>(basic_type<T>) 
        { 
            return std::is_integral_v<T>; 
        }, 
        basic_type<int>{});

    // This gives "x is not a constant expression"
    /*return foo(
        []<typename T>(T) 
        { 
            return std::is_integral_v<std::decay_t<T>>; 
        }, 
        0);*/
}

第一个 return 语句在最新的 gcc 主干上编译得很好,而第二个没有编译,错误消息:

source>: In instantiation of 'consteval auto foo(auto:1, auto:2) [with auto:1 = main()::<lambda(T)>; auto:2 = int]':
<source>:26:12:   required from here
<source>:9:3: error: 'x' is not a constant expression
    9 |   if constexpr (p(x)) {
      |   ^~
<source>: In function 'int main()':
<source>:26:19: error: 'consteval auto foo(auto:1, auto:2) [with auto:1 = main()::<lambda(T)>; auto:2 = int]' called in a constant expression
   26 |         return foo(
      |                ~~~^
   27 |                 []<typename T>(T)
      |                 ~~~~~~~~~~~~~~~~~
   28 |                 {
      |                 ~  
   29 |                         return std::is_integral_v<std::decay_t<T>>;
      |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   30 |                 },
      |                 ~~ 
   31 |                 0);
      |                 ~~ 
<source>:8:16: note: 'consteval auto foo(auto:1, auto:2) [with auto:1 = main()::<lambda(T)>; auto:2 = int]' is not usable as a 'constexpr' function because:
    8 | consteval auto foo(auto p, auto x) noexcept {
      |                ^~~

谁能告诉我为什么?

这是一个神螺栓链接 https://godbolt.org/z/71rbWob4e

编辑

根据要求,这里是没有自动参数的 foo:

template<typename Predicate, typename T>
consteval auto foo(Predicate p, T x) noexcept {
  if constexpr (p(x)) {
    return 1;
  } else {
    return 0;
  }
}

错误信息如下所示:


<source>: In instantiation of 'consteval auto foo(Predicate, T) [with Predicate = main()::<lambda(T)>; T = int]':
<source>:27:15:   required from here
<source>:10:3: error: 'x' is not a constant expression
   10 |   if constexpr (p(x)) {
      |   ^~
<source>: In function 'int main()':
<source>:27:15: error: 'consteval auto foo(Predicate, T) [with Predicate = main()::<lambda(T)>; T = int]' called in a constant expression
   27 |     return foo(
      |            ~~~^
   28 |         []<typename T>(T)
      |         ~~~~~~~~~~~~~~~~~
   29 |         {
      |         ~      
   30 |             return std::is_integral_v<std::decay_t<T>>;
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   31 |         },
      |         ~~     
   32 |         0);
      |         ~~     
<source>:9:16: note: 'consteval auto foo(Predicate, T) [with Predicate = main()::<lambda(T)>; T = int]' is not usable as a 'constexpr' function because:
    9 | consteval auto foo(Predicate p, T x) noexcept {
      | 

【问题讨论】:

  • fwiw,区别似乎是0 vs basic_type&lt;int&gt;{} 而不是std::decay_t&lt;T&gt; vs T (godbolt.org/z/5Mfe14MxW)
  • 鉴于所有编译器(gcc、clang、msvc)都有相同的结果和有点奇怪的规则 w.r.t. if constexpr 我倾向于相信这个错误是正确的(但不是 100% 肯定)。你可以尝试用实际的模板参数重写你的 foo 替换 auto 参数吗?可能会更清楚。
  • 我添加了没有自动参数@DanM的函数。
  • constevalconstexpr 函数可以有非编译时参数。这意味着编译器在编译此函数时会做出最一般的假设。这个假设是参数是非编译时的。这意味着编译器不能保证您的 if-constexpr 始终是编译时,因此会出现错误。如果您将所有参数作为模板auto 函数的参数,那么它应该编译良好。
  • 但它实际上并不依赖于你传入的值,只依赖于值的类型,这在编译时总是已知的?

标签: c++ c++20


【解决方案1】:

为了评估这个:

  if constexpr (p(x)) {

我们需要p(x) 是一个常量表达式。某事物是否符合常量表达式的规则基于您不允许做的事情的列表。

xbasic_type&lt;int&gt; 并且p 是一个函数,它接受basic_type&lt;int&gt; 的值,根本没有我们违反的规则。这是一个空类型,所以复制它(就像我们在这里所做的那样)实际上并不涉及任何类型的读取。这很有效。


但是当xint 并且p 是一个按值获取int 的函数时,这也需要复制x 但这一次它涉及读取x 的值。因为,当然,必须以某种方式初始化参数。这确实违反了规则:[expr.const]/8 说我们不允许执行:

左值到右值的转换,除非它被应用于

  • 一个非易失性泛左值,它引用一个可在常量表达式中使用的对象,或者
  • 文字类型的非易失性左值,它引用一个非易失性对象,其生命周期在 E 的评估中开始;

当我们读取变量的值时会发生左值到右值的转换,这些项目符号都不适用。在这里,您实际上并不关心值是什么并不重要,因为p 不使用它。为了能够调用p,你必须复制x,你不能这样做。因此出现错误。


但是,这里的 lambda 实际上不需要值,只需要类型,因此您可以改为这样写:

    return foo(
        []<typename T>(T const&) 
        { 
            return std::is_integral_v<std::decay_t<T>>; 
        }, 
        0);

现在我们不再将x 复制到 lambda 中,因为 lambda 不再按值获取 - 它通过引用获取。因此,我们没有违反左值到右值的转换规则(或任何其他规则),现在这是一个有效的常量表达式。


然后,作为奖励,如果您将 foo 更改为引用 x(因为,同样,您实际上并不关心值,所以为什么不关心):

consteval auto foo(auto p, auto const& x) noexcept {
  if constexpr (p(x)) {
    return 1;
  } else {
    return 0;
  }
}

然后 both 变体变得格式错误。 basic_type&lt;int&gt;int 版本(无论您是通过值还是通过引用来获取 int)。有关此案例的更多信息,请参阅 the constexpr array size problem,我目前正在尝试使用 P2280 解决此问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-03
    • 1970-01-01
    • 2016-07-29
    • 1970-01-01
    • 2020-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多