Is it possible to test if a constexpr function is evaluated at compile time?
Asked Answered
T

8

36

Since the extended versions of constexpr (I think from C++14) you can declare constexpr functions that could be used as "real" constexpr. That is, the code is executed at compile time or can behave as inline functions. So when can have this program:

#include <iostream>

constexpr int foo(const int s) {
  return s + 4;
}

int main()
{
    std::cout << foo(3) << std::endl;

    const int bar = 3;
    std::cout << foo(bar) << std::endl;

    constexpr int a = 3;
    std::cout << foo(a) << std::endl;

    return 0;
}

The result is:

7
7
7

So far so good.

Is there a way (possibly standard) to know inside foo(const int s) if the function is executed at compile time or at runtime?

EDIT: Also is it possible to know at runtime if a function was evaluated at compile time?

Tiber answered 24/10, 2017 at 20:37 Comment(11)
afaik you can enforce compile time if you use it eg as a template parameter, eg template <int x> struct bar {}; bar<foo(3)>;Think
This is an ongoing issue, unfortunately. I think there are some hacks that sort of work, but there is really no good way to discern whether or not a function is being evaluated in a constexpr context. :-/ Jason Turner has talked about this topic at length in a video where he talks about making a JSON parser that runs at compile time.Hornwort
@tobi303 - If you want to force it, you can just assign the output to a static const variable and then use that variable instead of the expression. No need to go to all the effort of having it be a template parameter.Hornwort
You can't tell if const int x in that context is a constexpr, so no.Noted
Look at the assembly output and see what your compiler did. :-)Hornwort
A bit ugly, but run it under a static_assert. Or assign its results to a constexpr object.Zorina
Thanks all, but I don't want to "force" it. I want to know. I supose I have to wait as @Hornwort said. I wanted to be sure there is no way, currently.Tiber
Omnifarious: Looking to the generated code is a "practical" solution, I can try with an online compiler, but I wanted to know if the language supports that.Tiber
@LeDYoM, and if you know, what are you going to do with it? ... constexpr functions were designed to fulfill an intent under certain constraints; And there are ways to ensure it does, for example - by assigning it to a constexpr variable; if we can't get the results at compile time, we have an error.. Why do you want to know whether it did or not without a compile error. How is it going to be useful to your program?Zorina
Related to Computing length of a C string at compile time. Is this really a constexpr?Playa
There's always consteval if you need guaranteed compile time evaluation.Bujumbura
N
28

The technique listed works, but since it uses static_assert it is not sfinae friendly. A better way (in theory, you'll see what I mean) to do this is to check whether a function is noexcept. Why? Because, constant expressions are always noexcept, even if the functions are not marked as such. So, consider the following code:

template <class T>
constexpr void test_helper(T&&) {}

#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))

test_helper is constexpr, so it will be a constant expression as long as its argument is. If it's a constant expression, it will be noexcept, but otherwise it won't be (since it isn't marked as such).

So now let's define this:

double bar(double x) { return x; }

constexpr double foo(double x, bool b) {
    if (b) return x; 
    else return bar(x);
}

foo is only noexcept if the x is a constant expression, and b is true; if the boolean is false then we call a non constexpr function, ruining our constexpr-ness. So, let's test this:

double d = 0.0;

constexpr auto x = IS_CONSTEXPR(foo(3.0, true));
constexpr auto y = IS_CONSTEXPR(foo(3.0, false));
constexpr auto z = IS_CONSTEXPR(foo(d, true));

std::cerr << x << y << z;

It compiles, great! This gives us compile time booleans (not compile failures), which can be used for sfinae, for example.

The catch? Well, clang has a multi-year bug, and doesn't handle this correctly. gcc however, does. Live example: http://coliru.stacked-crooked.com/a/e7b037932c358149. It prints "100", as it should.

Nitrate answered 24/10, 2017 at 21:12 Comment(12)
I think you have misunderstood the question. "So my question is: is there a way (possibly standard) to know in foo(const int s) if the function is beeing executed at compile time or at runtime?" (emphasis mine).Selinski
EDIT: I didn't spot the follow-up question.. which I think should be a separate question: "EDIT: Also is it possible to know at runtime if a function was evaluated at compile time?". IMO, you should declare in your answer that it only addresses that second issue.Selinski
@JohannesSchaub-litb Ah, you are saying that they want to know, from inside foo, whether it's being evaluated as part of a constant expression? The other answer doesn't address this either, and the example in the question doesn't mention it, which is why I was confused.Nitrate
Wait.. now that I read it again, he asks "to know at runtime if a function was evaluated at compile time?". I'm not sure what that means exactly at all.Selinski
@JohannesSchaub-litb Yeah, I'm not sure. I also considered the interpretation of literally whether the instructions get executed by the CPU at compile or runtime. It's a bit unclear, it's one of those questions where it would probably be better if OP told us what their endgame was. I think I'll leave this answer up as it's one reasonable interpretation, and it does contain useful information.Nitrate
So if I understand this correctly, test_helper exists because it's a non-noexcept function, so if noexcept(test_helper(expression)) is true, that means it can't be just because the expression is noexcept; it has to be because the expression is constexpr. This is pretty smartNyctaginaceous
This IS_CONSTEXPR macro seems to only work for expressions; it doesn't work for just variables: godbolt.org/g/zHvbBkNyctaginaceous
@Nyctaginaceous The problem with the variable is that it's not actually used, so the compiler is allowed to say that test_helper is a constant expression, even if one of its arguments is not. Offhand I don't see an easy way to truly "use" a generic input, without making additional assumptions. I do think there are also some compiler bugs though: surely this is wrong: godbolt.org/g/Gj8iyn.Nitrate
Interestingly, if you have test_helper take T by value instead of universal reference, it appears to workNyctaginaceous
this broke somewhere between gcc 8.2 and 9.1. prints 000. Clang still 000. MSVC has correct output.Imperative
Wait, how can a function be noexcept for one parameter value and potentially-throwing for another? Isn't foo simply noexcept(false), period?Wilheminawilhide
This is useful, so if I re-ask the question in the way you interpreted it, will you post the same answer there?Deglutinate
R
35

C++20 introduces is_constant_evaluated, defined in header <type_traits>, which addresses this issue.

constexpr int foo(int s)
{
    if (std::is_constant_evaluated()) // note: not "if constexpr"
        /* evaluated at compile time */;
    else
        /* evaluated at run time */;
}

Note that here the ordinary if is used instead of if constexpr. If you use if constexpr, then the condition has to be evaluated at compile time, so is_constant_evaluated always returns true, rendering the test useless.

Ramify answered 5/10, 2019 at 12:3 Comment(6)
It does not work coliru.stacked-crooked.com/a/61f3d3e62656525eMassachusetts
@ÖöTiib You are calling foo in a non-constexpr context, so this function correctly indicates that it is evaluated at run time.Ramify
So it does not detect if foo(42) is constant expression or isn't merely if I call foo(42) in context of evaluating constant expressions. If foo(42) (after possible overload resolution template argument deduction and so on) isn't constant expression but I call it in context of evaluating constant expression then my program is ill formed, while in code that I posted nothing is ill formed compiler just has opportunity to optimize my foo(42) == 42 out.Massachusetts
@ÖöTiib The question is "Is there a way (possibly standard) to know in foo(const int s) if the function is executed at compile time or at runtime?"Ramify
There is extended question: "Also is it possible to know at runtime if a function was evaluated at compile time?" It is important question since compiler may optimize run-time evaluation into compile time evaluation based on as-is rule.Massachusetts
@ÖöTiib I guess that won't be easily detectable - after all, the as-if rule is based on the very principle that optimizations should not be allowed if they modify the observable behavior of the program. SFINAE may help with language-level constant expressions, but probably not with optimizations.Ramify
N
28

The technique listed works, but since it uses static_assert it is not sfinae friendly. A better way (in theory, you'll see what I mean) to do this is to check whether a function is noexcept. Why? Because, constant expressions are always noexcept, even if the functions are not marked as such. So, consider the following code:

template <class T>
constexpr void test_helper(T&&) {}

#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))

test_helper is constexpr, so it will be a constant expression as long as its argument is. If it's a constant expression, it will be noexcept, but otherwise it won't be (since it isn't marked as such).

So now let's define this:

double bar(double x) { return x; }

constexpr double foo(double x, bool b) {
    if (b) return x; 
    else return bar(x);
}

foo is only noexcept if the x is a constant expression, and b is true; if the boolean is false then we call a non constexpr function, ruining our constexpr-ness. So, let's test this:

double d = 0.0;

constexpr auto x = IS_CONSTEXPR(foo(3.0, true));
constexpr auto y = IS_CONSTEXPR(foo(3.0, false));
constexpr auto z = IS_CONSTEXPR(foo(d, true));

std::cerr << x << y << z;

It compiles, great! This gives us compile time booleans (not compile failures), which can be used for sfinae, for example.

The catch? Well, clang has a multi-year bug, and doesn't handle this correctly. gcc however, does. Live example: http://coliru.stacked-crooked.com/a/e7b037932c358149. It prints "100", as it should.

Nitrate answered 24/10, 2017 at 21:12 Comment(12)
I think you have misunderstood the question. "So my question is: is there a way (possibly standard) to know in foo(const int s) if the function is beeing executed at compile time or at runtime?" (emphasis mine).Selinski
EDIT: I didn't spot the follow-up question.. which I think should be a separate question: "EDIT: Also is it possible to know at runtime if a function was evaluated at compile time?". IMO, you should declare in your answer that it only addresses that second issue.Selinski
@JohannesSchaub-litb Ah, you are saying that they want to know, from inside foo, whether it's being evaluated as part of a constant expression? The other answer doesn't address this either, and the example in the question doesn't mention it, which is why I was confused.Nitrate
Wait.. now that I read it again, he asks "to know at runtime if a function was evaluated at compile time?". I'm not sure what that means exactly at all.Selinski
@JohannesSchaub-litb Yeah, I'm not sure. I also considered the interpretation of literally whether the instructions get executed by the CPU at compile or runtime. It's a bit unclear, it's one of those questions where it would probably be better if OP told us what their endgame was. I think I'll leave this answer up as it's one reasonable interpretation, and it does contain useful information.Nitrate
So if I understand this correctly, test_helper exists because it's a non-noexcept function, so if noexcept(test_helper(expression)) is true, that means it can't be just because the expression is noexcept; it has to be because the expression is constexpr. This is pretty smartNyctaginaceous
This IS_CONSTEXPR macro seems to only work for expressions; it doesn't work for just variables: godbolt.org/g/zHvbBkNyctaginaceous
@Nyctaginaceous The problem with the variable is that it's not actually used, so the compiler is allowed to say that test_helper is a constant expression, even if one of its arguments is not. Offhand I don't see an easy way to truly "use" a generic input, without making additional assumptions. I do think there are also some compiler bugs though: surely this is wrong: godbolt.org/g/Gj8iyn.Nitrate
Interestingly, if you have test_helper take T by value instead of universal reference, it appears to workNyctaginaceous
this broke somewhere between gcc 8.2 and 9.1. prints 000. Clang still 000. MSVC has correct output.Imperative
Wait, how can a function be noexcept for one parameter value and potentially-throwing for another? Isn't foo simply noexcept(false), period?Wilheminawilhide
This is useful, so if I re-ask the question in the way you interpreted it, will you post the same answer there?Deglutinate
E
8

Within a constexpr function, you couldn't tell if you are being evaluated in a constexpr context prior to . Since , this functionalty was added -- constexpr bool std::is_constant_evaluated() will tell you if you are being called in a constexpr context.


Outside a constexpr function, there are a number of ways to determine if a call to a function with a certain set of arguments would be evaluated in a constexpr context. The easiest would be to use the result in a context requiring constexpr.

Assuming your constexpr expression returns a non-void integral or pointer type (including function pointer):

#define CONSTEXPR_EVAL(...) \
  std::integral_constant< \
    std::decay_t<decltype(__VA_ARGS__)>, \
    __VA_ARGS__ \
  >::value

then CONSTEXPR_EVAL( bar(foo, true) ) will fail to compile if bar(foo, true) cannot be evaluated at compile time, and if it can be evaluated at compile time it returns that value.

Other tricks involving noexcept (a function evaluated at compile time is noexcept) can work (see @NirFriedman's answer).

Eurythmics answered 25/10, 2017 at 19:0 Comment(2)
"Within a constexpr function, you could tell if you are being evaluated in a constexpr context prior to c++20" Is this simply a typo (could --> couldn't)? If not, could you please show how?Hyaline
@Hyaline detypoifiedEurythmics
A
7

I think the canonical way to do that is with static_assert. static_asserts are evaluated at compile time, so they will break the build if their condition is false.

#include <iostream>

constexpr int foo(const int s) {
  return s + 4;
}

int main()
{
    std::cout << foo(3) << std::endl;
    const int bar = 3;
    std::cout << foo(bar) << std::endl;
    constexpr int a = 3;
    std::cout << foo(a) << std::endl;

    static_assert(foo(3) == 7, "Literal failed");
    static_assert(foo(bar) == 7, "const int failed");
    static_assert(foo(a) == 7, "constexpr int failed");
    return 0;
}

clang++ -std=c++14 so1.cpp compiles fine for me, showing that everything works as expected.

Airbrush answered 24/10, 2017 at 20:45 Comment(4)
Thanks, it worked. To add something: I added the lines int b = 2; std::cout << foo(b) << std::endl; static_assert(foo(b) == 7, " int failed"); and it failed with "non constant expression in static assert".Tiber
I wish there was a cleaner way. constexpr is still pretty new, by C++ standards, so we are still trying to figure out best practices for using it.Airbrush
The problem here is that this absolutely forces the compiler to do everything it can to evaluate the constexpr at compile time. But it says nothing about whether or not the compiler is actually doing it when you call the function in a non-static_assert context. It just tells you that the compiler could do it if it wanted to. I've had a compiler (an older version of gcc) not bother to evaluate a constexpr function at compile time even though it was perfectly capable of doing so.Hornwort
Okay, I should say that it really depends on the question @Tiber is asking. I understood to be "I've got this function. If I call it here, can it be done at compile time?" At some level, as C++ devs, we have to trust that our compilers know best. Using the constexpr-ness of an expression as part of a SFINAE metaprogram is something else entirely. It's a more and more complicated subject the deeper you dive in to it.Airbrush
R
7

If you can use C++20, there is std::is_constant_evaluated which does exactly what you want. std::is_constant_evaluated is typically implemented using a compiler intrinsic.

This is called __builtin_is_constant_evaluated in GCC and clang, so you can implement your own "safe" wrapper around it, even in C++17 and lower.

// if C++20, we will need a <type_traits> include for std::is_constant_evaluated

#if __cplusplus >= 202002L
#include <type_traits>
#endif

constexpr bool is_constant_evaluated() {
#if __cplusplus >= 202002L
    return std::is_constant_evaluated();
#elif defined(__GNUC__) // defined for both GCC and clang
    return __builtin_is_constant_evaluated();
#else
    // If the builtin is not available, return a pessimistic result.
    // This way callers will implement everything in a constexpr way.
    return true;
#endif
}

Note that this builtin is still relatively new (GCC 9.0+) so you might also want to detect the compiler version.

Roaster answered 21/8, 2020 at 20:26 Comment(4)
C++ version detection is implemented differently in MSVC, so the #if defined (__GNUC__) should be on the outside.Deglutinate
@Deglutinate differently how? The __cplusplus macro is a standard macro. Every compiler is required to define it.Roaster
It's defined in MSVC but you can't interpret the value as conforming to a particular standard. It's just the version of the compiler.Deglutinate
In order to detect C++17 or C++20 you need to use #if _HAS_CXX17, #if _HAS_CXX20 learn.microsoft.com/en-us/cpp/preprocessor/…Deglutinate
G
1

Based on the information in this discussion, I crafted the following minimal example:

template <class T>
constexpr void test_helper(T &&) {}

#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))

constexpr void test(){
    static_assert(IS_CONSTEXPR(10), "asdfadsf");

    constexpr const int x = 10;
    static_assert(IS_CONSTEXPR(x), "asdfadsf");

    int y;
    static_assert(IS_CONSTEXPR(y), "asdfadsf");
}

int main(){
    test();
    return 0;
}

To my disappointment, It it fails to compile at each of the static_asserts. see https://www.godbolt.org/z/Tr3z93M3s

Groce answered 1/9, 2021 at 22:12 Comment(0)
A
1

C++23 has added if consteval for this exact purpose

constexpr int foo(const int s)
{
    if consteval
    {
        // Compile time
    }
    else
    {
        // Runtime
    }
}
Autogenesis answered 3/10, 2023 at 20:19 Comment(1)
A decent answer ... but note that the question is tagged with the explicit c++14 and c++17 language-standard tags.Cauthen
S
0

Sorry for spoiling the party, but there is certainly no standard way of doing this. Under the as-if rule, the compiler could emit code that calculates the result at run time even in such cases where it has already been forced to calculate it at compile time in a different context. Anything that can be done at compile time can be done again at run time, right? And the calculation has already been proven not to throw.

By extension, then, any standard-compliant IS_REALLY_CONSTEXPR or is_really_constexpr check cannot disprove that the exact same call, or for that matter, the value of the exact same constexpr symbol involves a run time calculation.

Of course there usually isn't any reason to repeat a calculation at run time that can be done or even has already been done at compile time, but the question was about telling whether the compiler uses the precalculated result, and there isn't one.

Now you did say possibly standard, so in effect your best bet is probably to test one of the provided solutions with your compiler of choice and hope that it behaves consistently. (Or reading the source code if it is open/public source and you are so inclined.)

Sundowner answered 25/10, 2017 at 12:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.