Search code examples
c++templatesmetaprogramming

Metaprogramming - power-like function


I want to define template which would behave similar to power function a^n

  1. a^n = -1 where a < 0 or n < 0
  2. a^0 = 0 (so not exactly as std::pow)
  3. otherwise std::pow

I have a problem defining the condition for point 1 - I assume this will be a combination of enable_if and some defined constexpr checking whether integer is negative.

What I wrote for the 1. point (commented out below) probably does not make sense as it do not compile. I am only starting with metaprogramming, to be honest I do not quite understand it. I would much appreciate if you could provide explanation and/or some resources you found helpful while getting into the topic.

#include <iostream>
#include <cmath>

// std::pow
template <int a, int n>
struct hc {
  enum { v = a * hc<a, n - 1>::v };
};
// to break recursion from getting to a^0=0 
template <int a>
struct hc<a, 1> {
  enum { v = a };
};

// a^0 = 0
template <int a>
struct hc<a, 0> {
  enum { v = 0 };
};

// a^n=-1 for negative a or n
/* 
template <int i>
constexpr bool is_negative = i < 0;

// a ^ n = -1, where a < 0 or n < 0
template <int a, int n,
          typename std::enable_if<is_negative<a> || is_negative<n>>::type>
struct hc {
  enum { v = -1 };
};
*/

int main() {
  // a^0=0
  std::cout << hc<0, 0>::v << " -> 0^0=0\n";
  std::cout << hc<3, 0>::v << " -> 3^0=0\n";

  // a^n=std::pow
  std::cout << hc<1, 1>::v << " -> 1^1=" << std::pow(1, 1) << '\n';
  std::cout << hc<2, 2>::v << " -> 2^2=" << std::pow(2, 2) << '\n';
  std::cout << hc<0, 2>::v << " -> 0^2=" << std::pow(0, 2) << '\n';
  std::cout << hc<3, 2>::v << " -> 3^2=" << std::pow(3, 2) << '\n';
  std::cout << hc<3, 7>::v << " -> 3^7=" << std::pow(3, 7) << '\n';

  // a^n=-1 for negative a or n
  std::cout << hc<-3, 7>::v << " -> -3^7=-1\n";
  std::cout << hc<3, -7>::v << " -> 3^-7=-1\n";
  std::cout << hc<0, -7>::v << " -> 0^7=-1\n";
  std::cout << hc<-3, 0>::v << " -> -3^0=-1\n";
}



Solution

  • There are several ways

    Simpler IMO, would be constexpr function

    constexpr int hc_impl(int a, int n)
    {
        if (a < 0 || n < 0) return -1;
        if (n == 0) return 0;
        int res = 1;
        for (int i = 0; i != n; ++n) {
           res *= a;
        }
        return res;
    };
    
    template <int a, int n>
    struct hc
    {
        constexpr int v = hc_impl(a, n);
    };
    

    The old way with struct, you might add an extra parameter for dispatch, something like:

    template <int a, int n, bool b = (a < 0 || n < 0)>
    struct hc;
    
    template <int a, int n>
    struct hc<a, n, true> {
      enum { v = -1 };
    };
    template <int a>
    struct hc<a, 1, true> {
      enum { v = -1 };
    };
    template <int a>
    struct hc<a, 0, true> {
      enum { v = -1 };
    };
    
    
    template <int a, int n>
    struct hc<a, n, false> {
      enum { v = a * hc<a, n - 1>::v };
    };
    // to break recursion from getting to a^0=0 
    template <int a>
    struct hc<a, 1, false> {
      enum { v = a };
    };
    
    // a^0 = 0
    template <int a>
    struct hc<a, 0, false> {
      enum { v = 0 };
    };