6

I wanted to calculate e value at compile–time (don't worry, not a homework), yet something went wrong.

template<size_t limit = 3, class result = std::ratio<0, 1>, size_t factorial = 1, size_t count = 1>
constexpr double e_impl() {
    if constexpr(limit == 0) {
        return static_cast<double>(result{}.num) / result{}.den;
    }
    return e_impl<limit - 1, std::ratio_add<result, std::ratio<1, factorial>>, factorial * count, count + 1>();
}

While calculated values are correct, compiler throws an error about overflow in template. It seems like limit variable goes out of range (below 0), but it shouldn't happen as the 0–case is being handled by the if constexpr(…) statement.

So the question is, am I wrong and that behavior should be expected, or is it a compiler bug? Compiled with GCC 7.1.0.

Kamil Koczurek
  • 696
  • 5
  • 15

2 Answers2

7

To avoid the error, put the second return explicitly into the else branch:

template<size_t limit = 3, class result = std::ratio<0, 1>, size_t factorial = 1, size_t count = 1>
constexpr double e_impl() {
    if constexpr(limit == 0) {
        return static_cast<double>(result{}.num) / result{}.den;
    }
    else
    {
      return e_impl<limit - 1, std::ratio_add<result, std::ratio<1, factorial>>, factorial * count, count + 1>();
    }
}

https://godbolt.org/g/PdV7m7

Rational:

During an instantiation of the enclosing function template or generic lambda, if the converted condition is true and the statement includes a constexpr else substatement, that substatement is not instantiated.

http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0128r1.html

It says nothing about an unconstrained else block, or a block which is not supposed to run, so it gets instantiated, throwing the error. (Note: also fails on clang)

erenon
  • 18,838
  • 2
  • 61
  • 93
4

No, this is not a bug. The issue here is that even when limit is 0 and you stop the recursion the compiler still stamps out

return e_impl<limit - 1, std::ratio_add<result, std::ratio<1, factorial>>, factorial * count, count + 1>();

as it is unconditional. You need to put it into a else block to get it only to compile when limit is not 0.

template<size_t limit = 3, class result = std::ratio<0, 1>, size_t factorial = 1, size_t count = 1>
constexpr double e_impl() {
    if constexpr(limit == 0)
        return static_cast<double>(result{}.num) / result{}.den;
    else
        return e_impl<limit - 1, std::ratio_add<result, std::ratio<1, factorial>>, factorial * count, count + 1>();
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402