Целое число в C++, метапрограммирование шаблонов

Я хочу сделать функцию, которая возвращает степень целого числа. Пожалуйста, прочитайте решение fmuecke в силу целого числа в C++.

Однако я хочу обобщить его решение на произвольный тип T. Поскольку в C++11 есть constexpr, я думаю, это возможно.

Наивно я пробовал что-то вроде

template<class T, int N>
inline constexpr T pow(const T x){
    return pow<N-1>(x) * x;
}
template<class T>
inline constexpr T pow<T, 1>(const T x){
    return x;
}
template<class T>
inline constexpr T pow<T, 0>(const T x){
    return 1;
}

На самом деле этот подход не удался, так как частичная специализация для шаблона функции не допускается.

И еще один вопрос. Я слышал, что от компилятора зависит, оценивается ли функция constexpr во время компиляции или нет. Как заставить его вычислить для общего типа. Я где-то читал, что один из самых простых способов взломать целочисленные константы - заключить его в std::integra_const::value.

6 ответов

Решение

Решение с использованием рекурсии:

#include <iostream>

template<class T>
inline constexpr T pow(const T base, unsigned const exponent)
{
    // (parentheses not required in next line)
    return (exponent == 0) ? 1 : (base * pow(base, exponent-1));
}

int main()
{
    std::cout << "pow(2, 4): " << pow(2, 4) << std::endl;
    std::cout << "pow(5, 0): " << pow(5, 0) << std::endl;
}

Jeremy W. Murphy предложил / запросил версию с возведением в степень путем возведения в квадрат:

template<class T>
inline constexpr T pow(const T base, unsigned const exponent)
{
    // (parentheses not required in next line)
    return (exponent == 0)     ? 1 :
           (exponent % 2 == 0) ? pow(base, exponent/2)*pow(base, exponent/2) :
           base * pow(base, (exponent-1)/2) * pow(base, (exponent-1)/2);
}

"Я слышал, что от компилятора зависит, оценивается ли функция constexpr во время компиляции".

Правда, AFAIK. Компилятору не требуется выполнять инициализацию константой во время компиляции, но если вы используете результат функции constexpr в качестве аргумента шаблона не типового типа, он должен вычислять результат во время компиляции.

std::cout << std::integral_constant<int, pow(2, 4)>::value << std::endl;

Также смотрите подход, используя integral_constant как параметр pow в ответ Andy Prowl.

Вот как вы можете применить оценку во время компиляции:

#include <iostream>
#include <type_traits>

// insert a constexpr `pow` implementation, e.g. the one from above

template < typename T, T base, unsigned exponent >
using pow_ = std::integral_constant < T, pow(base, exponent) >;

// macro == error prone, you have been warned
#define POW(BASE, EXPONENT) (pow_ < decltype(BASE), BASE, EXPONENT > :: value)

int main()
{
    std::cout << "pow(2, 4): " << pow_<int, 2, 4>::value << std::endl;
    std::cout << "pow(2, 4): " << POW(2, 4) << std::endl;
}

Пожалуйста, оставьте комментарий, если вы отрицаете, чтобы я мог улучшить свой ответ.

Если вам требуется частичная специализация шаблона функции (будьте осторожны, это не означает, что в этом случае вы нуждаетесь, как показывает ответ DyP), вы можете прибегнуть к перегрузке (см. Последнее обновление в конце этого документа). ответ) или, если это невозможно, оберните этот шаблон функции в шаблон класса, и пусть статическая функция, не являющаяся членом шаблона, заменит исходный шаблон функции (и его специализации):

namespace detail
{
    template<class T, int N>
    struct helper
    {
        static constexpr T pow(const T x){
            return helper<T, N-1>::pow(x) * x;
        }
    };

    template<class T>
    struct helper<T, 1> // Unnecessary specialization! (see the edit)
    {
        static constexpr T pow(const T x){
            return x;
        }
    };

    template<class T>
    struct helper<T, 0>
    {
        static constexpr T pow(const T x){
            return 1;
        }
    };
}

Затем вы можете предоставить шаблон вспомогательной функции, который делегирует специализацию вашего шаблона вспомогательного класса:

template<int N, class T>
T constexpr pow(T const x)
{
    return detail::helper<T, N>::pow(x);
}

Вот живой пример.

РЕДАКТИРОВАТЬ:

Обратите внимание, что специализация для N == 1 на самом деле не нужно. Я сохранил его в исходном тексте, потому что цель этого ответа состояла главным образом в том, чтобы показать, как обойти невозможность частичной специализации шаблонов функций в целом - поэтому я перевел оригинальную программу по частям.

Однако, как отметил Дип в комментариях, этого будет достаточно:

namespace detail
{
    template<class T, int N>
    struct helper
    {
        static constexpr T pow(const T x){
            return helper<T, N-1>::pow(x) * x;
        }
    };

    template<class T>
    struct helper<T, 0>
    {
        static constexpr T pow(const T x){
            return 1;
        }
    };
}

ОБНОВИТЬ:

В качестве еще одного замечания, пожалуйста, имейте в виду, что даже когда вы можете специализировать шаблоны функций (например, с явными - не частичными - специализациями), это, как правило, не очень хорошая идея, потому что специализация шаблонов функций обычно не ведет себя так, как это было бы. ожидать.

Большинство из тех ситуаций, в которых может потребоваться специализация шаблона функции, может быть достигнуто за счет перегрузки, основанной на хорошо известных методах, таких как диспетчеризация тегов. Потатосватер предложил в комментариях пример, указывающий, что std::integral_constant можно использовать в этой ситуации:

template<class T>
inline constexpr T pow(const T x, std::integral_constant<T, 0>){
    return 1;
}

template<class T, int N>
inline constexpr T pow(const T x, std::integral_constant<T, N>){
    return pow(x, std::integral_constant<T, N-1>()) * x;
}

template<int N, class T>
inline constexpr T pow(const T x)
{
    return pow(x, std::integral_constant<T, N>());
}

Однако все эти рекомендации о том, "как решать проблемы, требующие частичной специализации шаблона функции", должны приниматься во внимание, когда они действительно необходимы. В данном конкретном случае, как показал DyP в своем ответе, это не так.

Вот решение с одной функцией:

template <int N, class T> 
constexpr T pow(const T& x) 
{
    return N > 1 ? x*pow<(N-1)*(N > 1)>(x) 
                 : N < 0 ? T(1)/pow<(-N)*(N < 0)>(x) 
                         : N == 1 ? x 
                                  : T(1);
}

Чистое и простое решение здесь:

      #include <cstddef>

template<size_t N, size_t P>
struct pow_constexpr { constexpr static auto value = N * pow_constexpr<N, P-1>::value; };

template<size_t N>
struct pow_constexpr<N, 1> { constexpr static auto value = N; };

template<size_t N>
struct pow_constexpr<N, 0> { constexpr static auto value = 1; };

int main() {
    return pow_constexpr<2, 30>::value; // 1073741824
}

Вот простое решение:

#include<bits/stdc++.h>
using namespace std;

template<int N, int M>
struct Pow
{
    enum { res = N * Pow<N,M-1>::res};
};


template<int N>
struct Pow<N,0>
{
    enum {res = 1};
};
int main()
{
    cout<<Pow<2,3>::res<<"\n";
}

Еще один простой способ

template<class T>  
T Power(T a , T b) {
  T x,y;
  x = 1;
  y = a;
  while(b > 0) {
     if(b&1) x = (x*y);
     y = (y*y);
     b >>= 1;
  }
return x;
}

Время: журнал (б)

Другие вопросы по тегам