74

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?

7
  • AFAIK, when all arguments are constant expressions. Commented Jan 9, 2013 at 23:19
  • @chris And what if I write POW((unsigned __int64)2, 63). Would that still count as a constant expression? Commented Jan 9, 2013 at 23:21
  • 10
    @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. Commented Jan 9, 2013 at 23:21
  • 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. Commented Jan 9, 2013 at 23:23
  • @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. Commented Jan 9, 2013 at 23:34

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

15 Comments

@mcmcc try using the result as an non-type template parameter. this should cover integral constexpr. for float you need something else.
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.
@jogojapan: ...that's exactly what my answer says. You can check the ISO C++ link for more debate on the subject.
@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...
@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;
|
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;

18 Comments

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?
@cyberpunk_ Yes. In fact, you should assume it will be called at run-time as that's what current compilers seem to do.
@cyberpunk_ The arguments to constexpr functions are not constant expressions and so that wouldn't help.
@cyberpunk_: You should use references for that. (Not that copying a compile-time value is going to cost anything...)
@balki 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);}()
|
1

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 necessary. Some examples:

int arr[POW(1, 2)];            // yes, in array size
constexpr int x = POW(0, 2);   // yes, in init of constexpr variable
static_assert(POW(0, 2) == 0); // yes, in static_assert

std::cout << POW(0, 2);        // no
int x = POW(0, 2);             // no

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 that constant-expression is not the same as constant expression; the former is a grammar rule which appears in productions such as:

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

In short, 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.


Note: In C++11, the rules for constant evaluation were less restrictive, but 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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.