Using meta-programming to calculate the log2 but `compilation terminated`

Viewed 182

I'm new to TMP and I wrote a program using meta-programming to calculate the log2. I wrote a template struct power2 to calculate the power, a template class log2arr with an array in it to save the result, and a embedded template struct log2 to calculate log2 values.

#include <iostream>

template <int i>
struct power2
{
    enum
    {
        value = (1 << i)
    };
};

template <int n>
class log2arr
{
    int arr[n];

    template <int i>
    struct log2
    {
        log2<i - 1> _arr;
        enum
        {
            value = log2<i - 1>::value + (power2<log2<i - 1>::value>::value == i)
        };
        void print()
        {
            _arr.print();
            std::cout << value << std::endl;
        }
        void set()
        {
            _arr.set();
            arr[i] = value;
        }
    };

    template <>
    struct log2<1>
    {
        enum
        {
            value = 1
        };
        void print() {}
        void set() {}
    };

  public:
    int *getArr()
    {
        log2<n>().set();
        return arr;
    }
};

int main()
{
    log2arr<4> a = log2arr<4>();
    for(auto i : a.getArr()){
        cout << i;
    }
}

But the compiler only told me compilation terminated.

What does it mean? How can I solve it?

Any help would be greatly appreciated.

1 Answers

Your code has some problems and I'll show some of they, in no particular order.

(1) no full specializations are allowed inside a struct/class; so your can't full specialize log2 inside log2arr.

You can migrate log2 outside log2arr or, if you really want maintain it inside log2arr, you can transform the full specialization in an equivalent partial specialization (legal inside a struct/class); by example, as follows

  template <int I, typename = std::true_type>
  struct log2
   {
     // body of the struct
   };

  template <int I>
  struct log2<I, std::integral_constant<bool, I == 1>>
   {
     // body of the struct specialization
   };

(2) if you return a int * from getArr(), you loose the information regarding the array inside log2Arr class; so doesn't work the range based for loop (for(auto i : a.getArr())).

Unfortunately you can't return a C-style array (you can't return arr itself).

But you're using C++11 or newer (you tagged only C++ but you're using range based for loop, so you're using at least C++11) so I strongly suggest you to define arr as a std::array<int, N>, not as C-style array (not int arr[N]). And I strongly suggest you to return a reference to arr itself (with a std::array you can do it)

   private:
      using arrT = std::array<int, N>;

      arrT arr {};

      // ...

   public:

      arrT & getArr ()
       { /* ... */ return arr; }

and I also suggest to add a getArr() for const objects

  arrT const & getArr () const
   { /* ... */ return arr; }

(3) you can't manage the arr array (not static member of log2Arr) inside a method of the embedded structure log2

    void set()
    {
        _arr.set();
        arr[i] = value; // <--- arr is inaccessible
    }

A possible solution is pass arr as reference, so

    void set (arrT & a) // arrT = std::array<int, N>
     {
        _arr.set(a);
        a[i] = value;
     }

and (in log2<1>)

    void set (arrT &) {}

Obviously you have to call set() passing arr as argument, so

log2<N>().set(arr)

(4) the initialization of arr inside getArr() is a bad idea (IMHO) because you initialize arr every time you call getArr().

Moreover: you can't use arr inside another method (if you want to add another one) without initializing it inside the other method.

Suggestion: initialize arr, one time for all, inside an explicit costructor; by example

  log2arr ()
   { log2<N>().set(arr); }

so your getArr() methods simply becomes

  arrT & getArr ()
   { return arr; }

  arrT const & getArr () const
   { return arr; }

(5) with log2<I> that initialize arr[I] and log<1> that initialize nothing, your int arr[N] contains arr[0] and arr[1] values that are not inizialized.

You can initialize those values to zero writing

  int arr[N] {};

or (using std::array<int, N>)

  using arrT = std::array<int, N>;

  arrT arr {};
  //       ^^  <--- initialize all to zero

but you have to decide how to initialize in arr[0] and arr[1]

(6) there is no needs of initialize a as follows

log2arr<4> a = log2arr<4>();

You can simply write

log2arr<4> a;

---------------------------------------

The following is the code modified

#include <array>
#include <iostream>

template <int I>
struct power2
 { enum { value = (1 << I) }; };

template <int N>
class log2arr
 {
   private:
      using arrT = std::array<int, N>;

      arrT arr {};

      template <int I, typename = std::true_type>
      struct log2
       {
         log2<I-1> _arr;

         enum { value =   log2<I-1>::value
                        + (power2<log2<I-1>::value>::value == I) };

         void print ()
          { _arr.print(); std::cout << value << std::endl; }

         void set (arrT & a)
          { _arr.set(a); a[I] = value; }
       };

      template <int I>
      struct log2<I, std::integral_constant<bool, I == 1>>
       {
         enum { value = 1 };

         void print() {}
         void set(arrT &) {}
       };

   public:
      log2arr ()
       { log2<N>().set(arr); }

      arrT & getArr ()
       { return arr; }

      arrT const & getArr () const
       { return arr; }
 };

int main ()
 {
   log2arr<4> a;

   for ( auto i : a.getArr() )
      std::cout << i;

   std::cout << std::endl;
 }
Related