Home > Enterprise >  Metaprogramming - power-like function
Metaprogramming - power-like function

Time:11-24

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";
}


CodePudding user response:

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 };
};

CodePudding user response:

This is how I would do it using template constexpr:

template<int a, int n>
constexpr int pow()
{
    if ((a < 0) || (n < 0)) return -1;
    if (n == 0) return 0;

    int result = 1;
    for (int i = 0; i < n; i  ) result *= a;
    return result;
}

int main()
{
    static_assert(pow<0,0>() == 0);
    static_assert(pow<2, 0>() == 0);
    static_assert(pow<-1, 0>() == -1);
    static_assert(pow<1, -1>() == -1);
    static_assert(pow<2, 3>() == 8);
}

CodePudding user response:

Your partial specialization syntax for the last case is incorrect: you should have something inside <> after template<.....> struct hn.

So, something like this will almost work:

// 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<a, n> {
  enum { v = -1 };
};

enable_if::type is a type which you have to put into a position where it can be SFINAE'd, not just somewhere inside template<>. You typically put it either inside a function signature or inside a partial template specialization.

Like this:

// You have to change your general case definition.
// std::pow
template<int a, int n, typename /*DummyUnusedType*/ = void>
struct hc {
  enum { v = a * hc<a, n - 1>::v };
};

// ... your existing definitions here ...

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

You actually don't even need is_negative and typename:

struct hc<a, n, std::enable_if_t<(a < 0 || n < 0)>> {  // Parens are optional

The only remaining problem is that your <a, 0> case intersects with this one for negative as. You can restrict it for non-negative as using the same trick.

In general, though, constexpr function are superior, as suggested by other answers.

  • Related