67
votes

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?

2
AFAIK, when all arguments are constant expressions. - chris
@chris And what if I write POW((unsigned __int64)2, 63). Would that still count as a constant expression? - Byzantian
@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. - Mooing Duck
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. - Kerrek SB
@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. - GManNickG

2 Answers

95
votes

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.

21
votes

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;