When does a constexpr function get evaluated at compile time?
Asked Answered
H

3

73

Since it is possible that a function declared as constexpr can be called during run-time, under which criteria does the compiler decide whether to compute it at compile-time or during runtime?

template<typename base_t, typename expo_t>
constexpr base_t POW(base_t base, expo_t expo)
{
    return (expo != 0 )? base * POW(base, expo -1) : 1;
}

int main(int argc, char** argv)
{
    int i = 0;
    std::cin >> i;

    std::cout << POW(i, 2) << std::endl;
    return 0;
}

In this case, i is unknown at compile-time, which is probably the reason why the compiler treats POW() as a regular function which is called at runtime. This dynamic however, as convenient as it may appear to be, has some impractical implications. For instance, could there be a case where I would like the compiler to compute a constexpr function during compile-time, where the compiler decides to treat it as a normal function instead, when it would have worked during compile-time as well? Are there any known common pitfalls?

Hornwort answered 9/1, 2013 at 23:18 Comment(7)
AFAIK, when all arguments are constant expressions.Shavonneshaw
@Shavonneshaw And what if I write POW((unsigned __int64)2, 63). Would that still count as a constant expression?Hornwort
@chris: Actually, it's more complex than that I think. I think constexpr is only required to be evaluated when its result is used as a template parameter, array bound, or other integral constant. Any other time is an optimization. In fact, even when given constant expression arguments, it might be required to execute at runtime. constexpr int func(int p) { return !p ? 1 : throw std::exception("HI");} must be evaluated at runtime when given a non-zero input.Alexipharmic
Initializers that are constant expressions form part of the static initialization phase, e.g. constexpr int a = POW(5, 4);. That's essentially computed at compile time. But you can of course still use POW in other places.Hypermetropia
@MooingDuck: Unless the result of the function is being used in your aforementioned constant expression "requirerers", then it will give a compile-time error because of the exception.Chordophone
@GManNickG: I ran out of comment space, but yes :DAlexipharmic
related: #14294771Cantabile
W
109

constexpr functions will be evaluated at compile time when all its arguments are constant expressions and the result is used in a constant expression as well. A constant expression could be a literal (like 42), a non-type template argument (like N in template<class T, size_t N> class array;), an enum element declaration (like Blue in enum Color { Red, Blue, Green };, another variable declared constexpr, and so on.

They might be evaluated when all its arguments are constant expressions and the result is not used in a constant expression, but that is up to the implementation.

Wisnicki answered 9/1, 2013 at 23:25 Comment(15)
I'm trying to conceive of a general way to assert the compile-time evaluation, something like STATIC_ASSERT_CONSTEXPR( pow(2,3) ), but I'm not having a lot of luck...Sculpturesque
@Sculpturesque try using the result as an non-type template parameter. this should cover integral constexpr. for float you need something else.Palaeontography
"A constexpr function is evaluated at compile time if all its arguments are constant expressions" --Bjarne Stroustrup said on Jan 11, 2013 07:27 PM: isocpp.org/blog/2013/01/… ?Leia
Where in the Standard is there a guarantee that constexpr functions are evaluated at compile time if all arguments are constant expressions? I believe the only thing the Standard says is that constexpr functions can be used in contexts that must be evaluated at compile time, e.g. template arguments. Anything else is up to the compiler to decide. At least that is what I've believed so far.Diarrhea
@jogojapan: ...that's exactly what my answer says. You can check the ISO C++ link for more debate on the subject.Wisnicki
@Wisnicki Say I have a constexpr int f(); and then declare a constant expression constexpr int a = f();. Your answer suggests that a will be evaluated at compile time, because f is constexpr, all its arguments are too, and the result is used in a constexpr. But I believe this is necessarily true only if a is used in context that requires compile-time evaluation, e.g. a template argument. Also, I have seen the link, but since this is an SO answer, the relevant facts, esp. Standard quotes, should be put here.Diarrhea
@jogojapan: A constexpr variable is a constant expression, which requires compile-time evaluation. The standard quotes are non-normative notes; the requirements for constexpr functions to be evaluated at compile-time are implicitly made all over the standard whenever it talks about a constant expression.Wisnicki
A constant expression does not require compile-time evaluation. 5.19/4: Note: Although in some contexts, constant expressions must be evaluated during program translation, others may be evaluated during program executionDiarrhea
@jogojapan: That's a note, so its not normative... Back to your example, a could then be used as a template-argument or other context that requires compile-time evaluation, which I believe implicitly requires its evaluation to be always done at compile-time. But I see your point, you should ask at the ISO C++ post to get the attention from those involved in the making of this feature...Wisnicki
The note isn't normative, but if your statement above was true, the note would be downright wrong. Actually, Herb says in the ISO C++ post already that Bjarne should have said: "A constexpr function can be evaluated at compile time if all its arguments are constant expressions". This is a very important point. The Standard leaves it open whether the constant expression is evaluated at compile time. And that's why the question here asks: What do compilers actually do? What is the criterion compilers actually use? I'd like to know myself, too...Diarrhea
@jogojapan: According to both Bjarne and Herb back at the ISO C++ post, "The correct answer - as stated by Herb - is that according to the standard a constexpr function may be evaluated at compiler time or run time unless it is used as a constant expression, in which case it must be evaluated at compile-time.". That would mean that a in your example must be evaluated at compile-time, and doesn't make the note downright wrong. I'm not sure this is actually guaranteed by the standard at this point...Wisnicki
@Wisnicki Frankly, what Bjarne says in this statement is not extremely helpful. He implicitly introduces the new concept of is used as constant expression (as opposed to: is a constant expression). Every expression marked constexpr is a constant expression, but it is only guaranteed to be evaluated at compile-time if it is used as a constant expression (e.g. used as template argument). But what exactly does that mean?Diarrhea
@jogojapan: I'm inclined to think it means that you got it right, and you are only guaranteed compile-time evaluation if the constant expression is directly or indirectly used in a context that requires compile-time evaluation... Sorry I can't be of any more help.Wisnicki
@mcmcc: For your "something else", why not write a constexpr function that accepts a floating-point value (or in fact any type you like) and returns an integer, then use that inside a template non-type parameter. constexpr size_t check_nonintegral_constexpr(float v) { return sizeof v; } std::array<check_nonintegral_constexpr(pow(2.0, 4))> assertion;Mountie
"And the result is used in a constant expression as well". I was struggling with that. Thanks.Firehouse
C
24

The function has to be evaluated at compile-time when a constant expression is needed.

The simplest method to guarantee this is to use a constexpr value, or std::integral_constant:

constexpr auto result = POW(i, 2); // this should not compile since i is not a constant expression
std::cout << result << std::endl;

or:

std::cout << std::integral_constant<int, POW(i, 2)>::value << std::endl;

or

#define POW_C(base, power) (std::integral_constant<decltype(POW((base), (power)), POW((base), (power))>::value)

std::cout << POW_C(63, 2) << std::endl;

or

template<int base, int power>
struct POW_C {
  static constexpr int value = POW(base, power);
};

std::cout << POW_C<2, 63>::value << std::endl;
Cumshaw answered 9/1, 2013 at 23:24 Comment(18)
Does that mean that std::cout << POW(2, 63) << std::endl could end up not being evaluated during compile-time, since cout doesn't require a constant expression value?Hornwort
@cyberpunk_ Yes. In fact, you should assume it will be called at run-time as that's what current compilers seem to do.Cumshaw
Is there a way to force the compiler to evaluate it during compile-time? Preferably in one line. An anonymous constexpr perhaps?Hornwort
but then I would sacrifice type independenceHornwort
@cyberpunk_ you can write a macro I suppose. Although I would just prefer the two line constexpr auto method.Cumshaw
And there really is no one liner way of doing this? How about nesting your solution inside of another constexpr function? I would like to avoid macros whenever possible.Hornwort
@cyberpunk_ The arguments to constexpr functions are not constant expressions and so that wouldn't help.Cumshaw
How about this? An anonymous lambda std::cout << *[](){static constexpr auto expr = POW(1.113, 11); return &expr;}() << std::endl;Hornwort
@cyberpunk_ Don't return a pointer and instead return a value. That should work.Cumshaw
@cyberpunk_: It stops being a constant expression itself that way, though.Chordophone
@Cumshaw Why not? Doesn't static basically mean "place it on the data segment"? What's wrong with a pointer since it can't disappear? @Chordophone But the function will be evaluated at compile-time in any case, correct?Hornwort
@cyberpunk_: Right, just the result of the lambda is not a constant expression so there's a "wall" there of sorts. And returning a pointer there just doesn't make sense. You get nothing for it except now you have to dereference it all the time; just return the value directly.Chordophone
@Chordophone But I am dereferencing it right before the lambda. *[]. I did this to ensure that the return statement doesn't copy the object.Hornwort
@cyberpunk_: You should use references for that. (Not that copying a compile-time value is going to cost anything...)Chordophone
Is there a generic way to wrap any constexpr expressions to force compile time. Eg. compile_time(POW(i,j))? I might have wanted that to be compile time but probably forgot to declare i,j as constexpr. Can we catch those errors in compile time not having to write wrappers for every function?Sucker
@Sucker Here is what I came up with, but I am not sure if it's the best possible solution. I am also not sure if returning an rvalue reference actually works this way. #define FORCE_CT_EVAL(func) [](){constexpr auto ___expr = func; return std::move(___expr);}()Hornwort
@cyberpunk_ what prevents the compiler from calling operator() of the lambda object? This is exactly what I see with gdb. Not only that, operator() might not be constexpr itself and you might be forcing run-time evaluation.Rescind
Sadly, all of the template bases syntax tricks you show, do not work with constexpr double functions. So I have to define a named constexr variable in this case (your first solution).Besom
L
0

Even if you wrote POW(0, 2), that wouldn't ensure constant-evaluation. POW(0, 2) is a constant expression, but that just means it could be constant-evaluated if need be.

The relevant rules are in [expr.const] p20:

An expression or conversion is manifestly constant-evaluated if it is:

  • a constant-expression, or
  • the condition of a constexpr if statement ([stmt.if]), or
  • an immediate invocation, or
  • the result of substitution into an atomic constraint expression to determine whether it is satisfied ([temp.constr.atomic]), or
  • the initializer of a variable that is usable in constant expressions or has constant initialization ([basic.start.static]).

Note: In C++11, the rules were less restrictive, and constant expressions were much less powerful (see C++11 [expr.const] p4). This answer is based on the latest standard, which is more relevant to future readers.

Note that constant-expression is not the same as constant expression; it's a grammar rule which appears in productions such as:

label:
    attribute-specifier-seqopt case constant-expression :

In other words, constant-evaluation is about where an expression appears. In case POW(0, 2): constant-evaluation takes place; in std::cout << POW(0, 2);, it doesn't, or in C++11, it would have been up to the compiler (although in C++11, constexpr functions are so restrictive that it's very difficult to see a difference). In practice, compilers do the same in C++11 and in more recent standards.

Levator answered 8/3, 2024 at 9:32 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.