Metafunction для вычисления x ^ n и возврата целочисленного предела без переполнения, если это невозможно?

Рассмотрим следующий код:

template <std::intmax_t Base, std::intmax_t Exponent> 
struct integer_power_bounded
{
    static_assert(Exponent >= 0, 
                  "Error in 'integer_power_bounded': 'Exponent >= 0' is false");
    static constexpr std::intmax_t value = /* something */;
};

template <std::intmax_t Base> 
struct integer_power_bounded<Base, 0>
{
    static constexpr std::intmax_t value = 1;
};

Вместо /* something */ я хотел бы вернуть std::numeric_limits<std::intmax_t>::min() или std::numeric_limits<std::intmax_t>::max(), если Base^Exponent не может быть представлен std::intmax_t. Трудно, чтобы избежать переполнения во время вычисления, потому что они создают ошибки при компиляции.

Как это сделать (без повышения)?

Ответ 1

Версия на основе SFINAE:

#include <cstdint>
#include <cmath>
#include <limits>
#include <type_traits>

constexpr std::intmax_t integer_power(std::intmax_t base,
                                      std::intmax_t exponent)
{
    return (exponent == 0) ? 1 :
           (exponent % 2 == 0) ?  integer_power(base, exponent/2)
                                 *integer_power(base, exponent/2) :
           base*integer_power(base, exponent-1);
}

namespace detail
{
    template<std::intmax_t base, std::intmax_t exponent,
             std::intmax_t res = integer_power(base,exponent)>
    constexpr std::intmax_t pow_helper(int)
    {
        return res;
    }

    template<std::intmax_t base, std::intmax_t exponent>
    constexpr std::intmax_t pow_helper(...)
    {
        return (exponent%2 == 0 || base > 0)
               ? std::numeric_limits<std::intmax_t>::max()
               : std::numeric_limits<std::intmax_t>::min();
    }
}

template<std::intmax_t base, std::intmax_t exponent>
constexpr std::intmax_t integer_power_bounded()
{
    return detail::pow_helper<base,exponent>(0);
}

Пример использования:

#include <iostream>
int main()
{
    std::cout << sizeof(std::intmax_t) << '\n';

    constexpr auto p2t6 = integer_power_bounded<2, 6>();
    constexpr auto p2t62 = integer_power_bounded<2, 62>();
    constexpr auto p2t63 = integer_power_bounded<2, 63>();
    constexpr auto p2t64 = integer_power_bounded<2, 64>();
    constexpr auto p2t65 = integer_power_bounded<2, 65>();

    std::cout << "2^6 == " << p2t6 << '\n';
    std::cout << "2^62 == " << p2t62 << '\n';
    std::cout << "2^63 == " << p2t63 << '\n';
    std::cout << "2^64 == " << p2t64 << '\n';
    std::cout << "2^65 == " << p2t65 << '\n';

    constexpr auto pm2t6 = integer_power_bounded<-2, 6>();
    constexpr auto pm2t62 = integer_power_bounded<-2, 62>();
    constexpr auto pm2t63 = integer_power_bounded<-2, 63>();
    constexpr auto pm2t64 = integer_power_bounded<-2, 64>();
    constexpr auto pm2t65 = integer_power_bounded<-2, 65>();

    std::cout << "-2^6 == " << pm2t6 << '\n';
    std::cout << "-2^62 == " << pm2t62 << '\n';
    std::cout << "-2^63 == " << pm2t63 << '\n';
    std::cout << "-2^64 == " << pm2t64 << '\n';
    std::cout << "-2^65 == " << pm2t65 << '\n';
}

Вывод:

8
2^6 == 64
2^62 == 4611686018427387904
2^63 == 9223372036854775807
2^64 == 9223372036854775807
2^65 == 9223372036854775807
-2^6 == 64
-2^62 == 4611686018427387904
-2^63 == -9223372036854775808
-2^64 == 9223372036854775807
-2^65 == -9223372036854775808

Пояснение:

Константное выражение не может содержать Undefined Поведение [expr.const]/2:

  • операция, которая будет иметь поведение Undefined [Примечание: включая, например, переполнение целого числа со знаком, определенную арифметику указателя, деление на ноль или определенные операции сдвига - конечная нота];

Следовательно, всякий раз, когда неограниченный integer_power создает переполнение, выражение, используемое для объявления std::integral_constant, не является допустимым постоянным выражением; замена не выполняется, и используется функция возврата.