How to use implicit template type deduction
Asked Answered
R

2

10

I am trying to write a template to calculate the power of a number during compile time (I am not a template meta-programming expert so any comment is appreciated). Below is the code:

template<typename T, T X, uint64_t P>
struct Pow
{
    static constexpr T result = X * Pow<T,X, P - 1>::result;
};
template<typename T, T X>
struct Pow<T, X, 0>
{
    static constexpr T result = 1;
};
template<typename T, T X>
struct Pow<T, X, 1>
{
    static constexpr T result = X;
};

which I need to call like:

Pow<decltype(4), 4, 2>::result

Question: Is there any way in writing a helper template so that the call skips the decltype? For example:

Pow<4, 2>::result

I have read the following but so far I couldn't see an answer (it seems quite the opposite) this, this, and this.

Radicalism answered 18/2, 2019 at 13:41 Comment(6)
Not a template class, but would a constexpr function work equally well, in your case?Fanlight
Thank you for your answer. i was looking at that as well but i am finding it hard to force it to be used in a constrexpr fashion. this is what code looked like: template <typename T> constexpr T Pow(T num, unsigned int pow) { return pow == 0 ? 1 : num * Pow(num, pow - 1); }Radicalism
All that should be needed is your existing template class, as shown in the question, and a constexpr function that returns Pow<T, x, y>::result; which will be logically equivalent, at compile time, to what you have here.Fanlight
I am a bit more afraid of people using it in something std::cout << Pow<2,4> which to my understanding would circumvent the constrexpr-ness of the template. (i might be wrong and it might be the same but my understanding is that this is more contextually dependent as where to keep the constexpresness of it)Radicalism
Maybe the exponent should be an unsigned type, not a signed type. It will break on Pow<double, 2.0, -1>.Exclaim
@Exclaim Thank you, good catch. I will correct nowRadicalism
E
12

Starting from C++17, you can use an auto type for the X template value

template <auto X, int64_t P>
struct Pow
{
    static constexpr decltype(X) result = X * Pow<X, P - 1>::result;
};

template <auto X>
struct Pow<X, 0>
{
    static constexpr decltype(X) result = 1;
};

And you can also see that, given the 0 partial specialization, the 1 partial specialization is superfluous (also C++11/C++14).

Before C++17... the best I can imagine, to avoid to explicit the T type, pass through a macro definition (that usually is heavily discouraged but, in this case, I suppose can be reasonable).

Something as

#define PowMacro(X, P)  Pow<decltype(X), X, P> 
Ethaethan answered 18/2, 2019 at 13:47 Comment(2)
"The 1 partial specialization is superfluous"... unless T is a class type representing some sort of mathematical ring without unit or semigroup, or otherwise for some reason does not support initialization from the expression 1.Exclaim
@Exclaim - good point... from the theoretical point of view... but, practically speaking, which T type, the values of witch can be template parameters, can't accept initialization from 1 but support multiplication?Ethaethan
E
7

Sure you can skip the decltype, and you need no structures when using C++ 11 contexpr. For example:

#include <iostream>
#include <type_traits>

template<typename T, class = typename std::enable_if< std::is_arithmetic<T>::value >::type >
constexpr T pow(T n, T power) noexcept {
    return power == 1 ? n : n * pow(n,power - 1);
}

int main(int argc, const char* argv) {

    static_assert( 4 == pow(2,2) ,"wrong pow");
    static_assert( 8.0F == pow(2.0F,3.0F) ,"wrong pow");
    static_assert( 256.0 == pow(2.0,8.0) ,"wrong pow");

    std::cout << "integer 2^2=" << pow(2, 2) << std::endl;
    std::cout << "float 2^3=" << pow(2.0F, 3.0F) << std::endl;
    std::cout << "double 2^8=" << pow(2.0, 8.0) << std::endl;

    return 0;
}

P.S. Faster way for racing number in a power. Real code should use something like that since compilation time also does matters.

#include <iostream>
#include <type_traits>

// https://en.wikipedia.org/wiki/Exponentiation_by_squaring
template<typename T>
constexpr T pow(const T base,const T power, typename std::enable_if< std::is_integral<T>::value >::type* = 0) {
    return  1 == power
            ? base
            : 0 == power
              ? 1
              : (1 == (power & 1) )
                ? base * pow(base, power - 1)
                : pow(base, (power >> 1) ) * pow( base, (power >> 1) );
}


#ifdef __GNUG__

  // GCC able to use most of <cmath> at compile time, check <cmath> header

  inline constexpr float pow(float base, float power) noexcept {
    return __builtin_powf(base, power);
  }

  inline constexpr double pow(double base, double power) noexcept {
    return __builtin_pow(base, power);
  }

  inline constexpr long double pow(long double base,long double power) noexcept {
    return __builtin_powl(base, power);
  }

#else

// slow
template<typename T>
constexpr T pow(T base, T power, typename std::enable_if< std::is_floating_point<T>::value >::type* = 0) noexcept {
    return power == 1.0 ? base : base * pow(base,power - static_cast<T>(1.0) );
}

#endif // __GNUG__


int main(int argc, const char** argv) {

    static_assert( 4 == pow(2,2) ,"wrong pow");
    static_assert( 1024 == pow(2L,10L) ,"wrong pow");
    static_assert( (1 << 20) == pow(2LL,20LL) ,"wrong pow");

    std::cout << "integer 2^1=" << pow(2, 1) << std::endl;
    std::cout << "integer 2^2=" << pow(2, 2) << std::endl;
    std::cout << "long 2^10=" << pow(2L, 10L) << std::endl;
    std::cout << "long long 2^20=" << pow(2LL, 20LL) << std::endl;

    static_assert( 8.0F == pow(2.0F,3.0F) ,"wrong pow");
    static_assert( 256.0 == pow(2.0,8.0) ,"wrong pow");
    static_assert( 1024.0L == pow(2.0L,10.0L) ,"wrong pow");

    std::cout << "float 2^3=" << pow(2.0F, 3.0F) << std::endl;
    std::cout << "double 2^8=" << pow(2.0, 8.0) << std::endl;
    std::cout << "long double 2^10=" << pow(2.0L, 10.0L) << std::endl;

    return 0;
}
Elberfeld answered 18/2, 2019 at 13:56 Comment(5)
Thank you for your answer. I have also read this but i have a question. Without the static_assert bit there would be no way to guarantee the compile time calculated. Is this correct?Radicalism
constexpr so in case of something like long double foo(long double bar, float buz) { return log(bar,MATH_PI) * pow(bar, sqrt(buz) ) ; } off cause it is not going to be a compile time. But in this case i think - it should not be a compile time. Unlike something like static constexpr long double FOO = pow(128.0,500.0D);Elberfeld
BTW, the best way to check - compiler assembly output.Elberfeld
If you want a compile time error when the arguments to pow are not constexpr, you can just static_assert(n == n, "not constexpr call"); static_assert(power == power, "not constexpr call");Quiteris
when you invoke the function in a compile-time context (either by storing the result as constexpr or by using static_assert) your compiler will automatically fail to compile with some kind of "no constexpr"-message. I don't see any value in checking the parameters for constants...Mastrianni

© 2022 - 2024 — McMap. All rights reserved.