Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function is not usable as a 'constexpr' function

Tags:

c++

c++20

Take a look at the following code

#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);*/
}

The first return statement compiles just fine on latest gcc trunk, while the second one does not compile, with the error message:

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 {
      |                ^~~

Can anyone tell me why?

Here's a godbolt link https://godbolt.org/z/71rbWob4e

EDIT

As requested, here's foo without auto parameters:

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

Error Message looks like this:


<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 {
      | 
like image 859
Yamahari Avatar asked Mar 30 '21 10:03

Yamahari


People also ask

Can a function be constexpr?

A constexpr function is a function that can be invoked within a constant expression. A constexpr function must satisfy the following conditions: It is not virtual. Its return type is a literal type.

How do I know if a function is constexpr?

The easiest way to check whether a function (e.g., foo ) is constexpr is to assign its return value to a constexpr as below: constexpr auto i = foo(); if the returned value is not constexpr compilation will fail.

What does constexpr mean?

constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.

Can a constexpr function call a non constexpr function?

A call to a constexpr function produces the same result as a call to an equivalent non- constexpr function , except that a call to a constexpr function can appear in a constant expression. The main function cannot be declared with the constexpr specifier.

What is constexpr function in C++?

constexpr functions. A constexpr function is one whose return value can be computed at compile time when consuming code requires it. Consuming code requires the return value at compile time, for example, to initialize a constexpr variable or provide a non-type template argument.

Should I declare a function constexpr?

In most cases you also cannot argue that you may prefer not to declare a function constexprsimply because you do not envisage any compile-time usage: because if others evtl. will use your code, they may see such a use that you do not. (But granted for type trait types and stuff alike, of course.)

When does a non-constexpr function produce a value at compile time?

When called with non- constexpr arguments, or when its value isn't required at compile time, it produces a value at run time like a regular function. (This dual behavior saves you from having to write constexpr and non- constexpr versions of the same function.) A constexpr function or constructor is implicitly inline.

What is the difference between constexpr and non-constexpr?

When its arguments are constexpr values, a constexpr function produces a compile-time constant. When called with non-constexpr arguments, or when its value isn't required at compile-time, it produces a value at run time like a regular function.


1 Answers

In order to evaluate this:

  if constexpr (p(x)) {

We need for p(x) to be a constant expression. The rules for whether something qualifies as a constant expression or not are based on a list of things that you're not allowed to do.

When x is a basic_type<int> and p is a function that takes a basic_type<int> by value, there are simply no rules that we are violating. This is an empty type, so copying it (as we're doing here) doesn't actually involve any kind of read. This just works.


But when x is an int and p is a function that takes an int by value, this also requires copying x but this time it involves reading the value of x. Because, of course, gotta initialize the parameter somehow. And this does run afoul of a rule: [expr.const]/8 says we're not allowed to perform:

an lvalue-to-rvalue conversion unless it is applied to

  • a non-volatile glvalue that refers to an object that is usable in constant expressions, or
  • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;

An lvalue-to-rvalue conversion is what happens when we read the value of a variable, and neither of those bullets apply. It doesn't matter here that you don't actually care what the value is, since p doesn't use it. In order to be able to even invoke p, you have to copy x, and you're not allowed to do that. Hence the error.


However, your lambda here doesn't actually need the value, just the type, so you could instead write this:

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

Now we're no longer copying x into the lambda, since the lambda no longer takes by value - it takes by reference. As a result, we're not violating the lvalue-to-rvalue conversion rule (or any other rule) and this is now a valid constant expression.


Then, as a bonus, if you change foo to take x by reference (because, again, you don't actually care about the value, so why not):

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

Then both variants become ill-formed. Both the basic_type<int> and int versions (regardless of whether you take the int by value or by reference). For more on this case, see the constexpr array size problem which I'm currently trying to resolve with P2280.

like image 93
Barry Avatar answered Oct 09 '22 20:10

Barry