Eliminate redundancy with CRTP and multiple inheritance

516 views Asked by At

This question is for C++03, not C++11.

I have a case where I am using CRTP with multiple inheritance, and I am curious to know if there is a way to remove the redundancy that is created when specifying the type of B below.

#include "boost/typeof/typeof.hpp"
#include "boost/units/detail/utility.hpp"
#include <iostream>
#include <string>

struct One{};
struct Two{};

template<typename T>
struct Type
{
   static std::string name(void)
   {
      return boost::units::detail::demangle(typeid(T).name());
   }
};

template<typename T1,
         typename T2>
struct A
{
   typedef A<T1, T2> Self;

   A()
   {
      std::cout << Type<Self>::name() << std::endl;
   }
};

template<typename T1,
         typename T2,
         typename T3>
struct B : public A<One, B<T1, T2, T3> >, // The B<T1, T2, T3> here is redundant
           public A<Two, B<T1, T2, T3> >
{
   typedef B<T1, T2, T3> Self;

   B()
   {
      std::cout << Type<Self>::name() << std::endl;
   }
};

int main(int argc, char* argv[])
{
   B<int, int, int> t;
   return 0;
}

See this on Coliru

The problem worsens when the number of template parameters for B increases, when the template arguments themselves are complex, and when B inherits from A more times. I'd like to minimize the repetition of B's template parameters. Specifically, I am looking for a way to access the typedef B<T1, T2, T3> Self up in the inheritance list for B, or some equivalent compile-time version of this.

I cannot:

  • Make a typedef for B above B using a forward declaration, because I don't have access to the template parameters
  • Make a typedef inside of the inheritance definition because the syntax doesn't allow that
  • Access the typedef from inside the class, because it doesn't exist yet

Something like the below (none of which are not valid code, but display the effect I am looking for):

template<typename T1,
         typename T2,
         typename T3>
struct B : public A<One, Self>, // Cannot access the typedef yet
           public A<Two, Self>
{
   typedef B<T1, T2, T3> Self;
};

template<typename T1,
         typename T2,
         typename T3>
struct B : typedef B<T1, T2, T3> Self, // Invalid syntax
           public A<One, Self>, 
           public A<Two, Self>
{

};

template<typename T1,
         typename T2,
         typename T3>
struct B : public A<One, B>, // I wish this would work
           public A<Two, B>
{

};

template<typename T1,
         typename T2,
         typename T3>
struct B : public A<One, BOOST_TYPEOF(*this)>, // lol
           public A<Two, BOOST_TYPEOF(*this)>
{

};

Is there a way to access a compile-time version of this?

1

There are 1 answers

0
Mike Kinghan On BEST ANSWER

The problem with:

template<typename T1,
         typename T2,
         typename T3>
struct B : public A<One, B>, // I wish this would work
           public A<Two, B>
{

};

is that your template <typename T1, typename T2> struct A asks to be instantiated with T2 a type, whereas what you wish you could do is instantiate it with T2 a template, namely template<typename, typename,typename> struct B.

If the definition of A lies in your own control, then perhaps - though perhaps not - a solution is to make the definition of A consistent with your wish:

#include "boost/typeof/typeof.hpp"
#include "boost/units/detail/utility.hpp"
#include <iostream>
#include <string>

struct One{};
struct Two{};

template<typename T>
struct Type
{
   static std::string name(void)
   {
      return boost::units::detail::demangle(typeid(T).name());
   }
};

template<typename T1,
         template<typename, typename, typename> class T2
>
struct A
{

   A()
   {
      std::cout << Type<A>::name() << std::endl;
   }
};


template<typename T1,
         typename T2,
         typename T3>
struct B : public A<One, B >,
           public A<Two, B >
{
   B()
   {
      std::cout << Type<B>::name() << std::endl;
   }
};


int main(int argc, char* argv[])
{
   B<int, int, int> t;
   return 0;
}

This program prints:

A<One, B>
A<Two, B>
B<int, int, int>

The price of this solution is restricting the classes for which A can furnish a CRTP base to ones that instantiate a template, like B, of exactly three typename parameters.

Perhaps you are lucky enough that this restriction does not thwart any other wishes you have. But if you also need A to furnish a CRTP base for classes that instantiate some template that does not have exactly three typename parameters, then it bites.

Provided that all of the classes for which you need A to furnish a CRTP base are instantiations of templates that have only typename parameters, and have at most N of them, then you can still have a C++03 solution in the same spirit:

You define A as per the schema:

template<typename T1,
         template<typename /*1*/,.... typename /*N*/> class T2
>
struct A { ... };

And for each template Y for which A is to be a CRTP base, you provide exactly N parameters, employing "padding" parameters that default to void, as necessary. For example, if N == 3:

#include "boost/typeof/typeof.hpp"
#include "boost/units/detail/utility.hpp"
#include <iostream>
#include <string>

struct One{};
struct Two{};

template<typename T>
struct Type
{
   static std::string name(void)
   {
      return boost::units::detail::demangle(typeid(T).name());
   }
};

template<typename T1,
         template<typename, typename, typename> class T2
>
struct A
{

   A()
   {
      std::cout << Type<A>::name() << std::endl;
   }
};


template<typename T1, typename T2 = void, typename T3 = void>
struct B : public A<One, B >,
           public A<Two, B >
{
   B()
   {
      std::cout << Type<B>::name() << std::endl;
   }
};

template<typename T1, typename T2, typename T3 = void>
struct C : public A<One, C >,
           public A<Two, C >
{
   C()
   {
      std::cout << Type<C>::name() << std::endl;
   }
};

template<typename T1, typename T2, typename T3>
struct D : public A<One, D >,
           public A<Two, D >
{
   D()
   {
      std::cout << Type<D>::name() << std::endl;
   }
};



int main(int argc, char* argv[])
{
   B<int> b;
   C<int,int> c;
   D<int,int,int> d;
   return 0;
}

This program prints:

A<One, B>
A<Two, B>
B<int, void, void>
A<One, C>
A<Two, C>
C<int, int, void>
A<One, D>
A<Two, D>
D<int, int, int>

True, the more general solution sticks you with a different kind of "redundancy", in the form of those superfluous defaulted template parameters. But you may find it a less irksome kind.

(gcc 5.1/clang 3.6, C++03)