c++recursionlanguage-lawyerconstexprcompile-time-constant

constexpr pow() function compiler fail


If I try something like:

template <auto X, auto E>
constexpr decltype(X) const_pow() noexcept
{
  return !E ? 1 : 1 == E ? X : (E % 2 ? X : 1) * const_pow<X * X, E / 2>();
}

I get an error:

prog.cc: In instantiation of 'constexpr decltype (X) const_pow() [with auto X = 100000000; auto E = 0; decltype (X) = int]':
prog.cc:6:73:   recursively required from 'constexpr decltype (X) const_pow() [with auto X = 100; auto E = 1; decltype (X) = int]'
prog.cc:6:73:   required from 'constexpr decltype (X) const_pow() [with auto X = 10; auto E = 3; decltype (X) = int]'
prog.cc:11:32:   required from here
prog.cc:6:62: warning: integer overflow in expression of type 'int' results in '1874919424' [-Woverflow]
    6 |   return !E ? 1 : 1 == E ? X : (E % 2 ? X : 1) * const_pow<X * X, E / 2>();
      |                                                            ~~^~~
prog.cc:6:73: error: no matching function for call to 'const_pow<(100000000 * 100000000), (0 / 2)>()'

The workaround is straightforward (e.g. requires, or if constexpr), but why does the compiler not end recursion by itself?


Solution

  • [temp.inst]/5:

    Unless a function template specialization is a declared specialization, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program.

    [basic.def.odr]/11:

    Every program shall contain at least one definition of every function or variable that is odr-used in that program outside of a discarded statement; no diagnostic required.

    [basic.def.odr]/8:

    A function is odr-used if it is named by a potentially-evaluated expression or conversion.

    [basic.def.odr]/4.1:

    A function is named by an expression or conversion if it is the selected member of an overload set in an overload resolution performed as part of forming that expression or conversion [...]

    The call in the conditional expression odr-uses const_pow<...> regardless of whether the branch is taken or not, causing its instantiation.