C++23: Consteval if to make compile time programming easier

Sandor Dargo - Jun 1 '22 - - Dev Community

Let's continue our exploration of C++23 features! This week we discuss how to call consteval functions from not explicitly constant evaluated ones.

This paper, this new feature is also a good example to see how C++ evolves. C++20 introduced 2 new keywords, consteval and constinit. Although they've been good additions, in the meanwhile the community found some bugs and also came up with some ideas for improvement. And here they are shipped with the next version of C++!

What is if consteval?

The amount of const* syntax is clearly growing in C++. const was part of the original language and then we got constexpr with C++11. C++17 introduced if constexpr, C++20 brought us consteval and constinit, and with C++23 we are going to get if consteval (often referred to as consteval if).

Let's see what the latest addition is about.

A consteval if statement has no condition. Better to say, it's the condition itself. If it's evaluated in a manifestly constant-evaluated context, then the following compound statement is executed. Otherwise, it's not. In case there is an else branch present, it will be executed as you'd expect it.

If that helps readability, you can also use if !consteval. The following two pieces of code are equivalent.

if !consteval {
  foo(); 
} else {
  bar();
}

// same as

if consteval {
  bar();
} else {
  foo();
}
Enter fullscreen mode Exit fullscreen mode

How to call consteval functions?

To answer that question, let's remind us of the difference between a constexpr and a consteval function. A constexpr function's return value can be computed at compile-time or during run-time. A consteval function is guaranteed to be executed during compile time, it's also called an immediate function.

In C++, we have a tendency of moving more and more computations to compile time. As such we slightly increase the compile-time (although it still goes down due to better compilers and more powerful computers), but we speed up the runtime. Following these trends and benefitting from compile-time computations, you might want to call consteval functions from constexpr functions. But it's not going to work with C++20.

consteval int bar(int i) {
    return 2*i;
}

constexpr int foo(int i) {
    return bar(i);
}

int main() {
  [[maybe_unused]] auto a = foo(5);
}
/* 
In function 'constexpr int foo(int)':
error: 'i' is not a constant expression
      |        return bar(i);
      |                ~~~^~~
*/
Enter fullscreen mode Exit fullscreen mode

It makes sense. After all, as foo(int) is a constexpr function, it can be executed at runtime too. But what if you really want to call a consteval function from a constexpr function when it's executed at compile time?

In C++20, consteval functions could invoke constepxr ones, but not the other way around. Even if you try to surround the call of the consteval function with std::is_constant_evaluated(), it won't change. The following example is not going to work, because i is still not a constant expression:

consteval int bar(int i) {
    return 2*i;
}

constexpr int foo(int i) {
    if (std::is_constant_evaluated()) {
        return bar(i);
    }
    return 2*i;
}

int main() {
  [[maybe_unused]] auto a = foo(5);
}
/*
main.cpp: In function 'constexpr int foo(int)':
main.cpp:6:14: error: 'is_constant_evaluated' is not a member of 'std'
    6 |     if (std::is_constant_evaluated()) {
      |              ^~~~~~~~~~~~~~~~~~~~~
main.cpp:7:19: error: 'i' is not a constant expression
    7 |         return bar(i);
      |                ~~~^~~

*/
Enter fullscreen mode Exit fullscreen mode

This proposal fixes it, by adding the new language feature of if consteval. Use that to call consteval functions from constexpr ones. In fact, not only from constexpr ones but from any function. Just make sure that you set the -std=c++2b compiler flag.

consteval int bar(int i) {
    return 2*i;
}

int foo(int i) {
    if consteval {
        return bar(i);
    }
    return 2*i;
}

int main() {
  [[maybe_unused]] auto a = foo(5);
}
Enter fullscreen mode Exit fullscreen mode

While if consteval behaves exactly as if (std::is_constant_evaluated), it's superior to it because it doesn't need any header include, its syntax is crystal clear, plus you can invoke consteval functions if it evaluates to true.

Conclusion

In this article, we learnt about a new C++ feature, if consteval that will help us invoke consteval functions when the context is constant-evaluated, yet it's not explicitly declared so.

Connect deeper

If you liked this article, please

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .