views:

105

answers:

2

I have a decorator-like pattern with a base that requires a constructor parameter. The decorator is constructed such that it can take an arbitrary number of add-on components as template parameters (up to 3 in this example).

Unfortunately, I can't figure out how to pass the base's constructor parameter to it when more than one add-on is specified. In the example below, CMyClass< AddOn_A > A( 100 ); works perfectly, but CMyClass< AddOn_A, AddOn_B > AB( 100 ); generates an error at the CMyClass constructor.

template< class Base >
class AddOn_A : public Base
{
public: 
    AddOn_A( int x ) : Base( x )
    {
    };

    int AddOne()
    {
        return static_cast< Base* >( this )->DoSomething() + 1;
    };
};

template< class Base >
class AddOn_B : public Base
{
public: 
    AddOn_B( int x ) : Base( x )
    {
    };

    int AddTwo()
    {
        return static_cast< Base* >( this )->DoSomething() + 2;
    };
};

class CBase
{
public:
    explicit CBase( int x ) : x_( x )
    {
    };

    int DoSomething()
    {
        return x_;
    };

private:
    int x_;
};

// define an empty AddOn
template< class > class empty {};

// forward declaration and Add-On defaults
template< template< class > class AddOn1 = empty,
          template< class > class AddOn2 = empty,
          template< class > class AddOn3 = empty >
class CMyClass;

// specialized template for the default case
template<> class CMyClass< empty, empty, empty > {};

// actual definition
template< template< class > class AddOn1,
          template< class > class AddOn2,
          template< class > class AddOn3 >
class CMyClass : public AddOn1< CBase >,
                 public CMyClass< AddOn2, AddOn3 >
{
public:
    // what needs to go here???
    CMyClass( int x ) : AddOn1< CBase >( x )
    {};
};

int _tmain( int argc, _TCHAR* argv[] )
{
    // works
    CMyClass< AddOn_A > A( 100 );
    _ASSERT( A.AddOne() == 101 );

    // works
    CMyClass< AddOn_B > B( 100 );
    _ASSERT( B.AddTwo() == 102 );

    // generates an error at the CMyClass ctor:
    // error C2512: 'CMyClass<AddOn1>' : no appropriate default constructor available
    CMyClass< AddOn_A, AddOn_B > AB( 100 );
    _ASSERT( AB.AddOne() == 101 );
    _ASSERT( AB.AddTwo() == 102 );

    return 0;
}

If anybody can point out what I may be doing wrong, please let me know.

Thanks, PaulH

A: 
  • It is not immediately clear what you are trying to achieve. I hope you are not mixing up inheritance and templates.

  • empty, AddOn_A and AddOn_B are class templates. They are not instances of a class. You need to have actual classes.

dirkgently
I don't think I'm confused. Each add-on suppliments CBase with additional functionality. I'm using a recursive template pattern to make it easier for the user to use without incurring a run-time penalty. Besides, it works great for one addon it just needs some tweaking for multiple addons.
PaulH
+2  A: 

Your errors are generally originating out of the fact that CMyClass does not have a default constructor (because you define a CMyClass(int) instead), so it is necessary to explicitly instantiate your parents with the CMyClass(int) constructor that you have. So, for example, in your definition of CMyClass you need to add the call to CMyClass(int) in the initializer list

CMyClass(int x) : AddOn1<CBase>(x), CMyClass<AddOn2, AddOn3>(x) //send x down

Now that we have CMyClass sending x down the line, it is necessary for your base case specialization (CMyClass<empty, empty, empty>) to now have a constructor that accepts x but does nothing with it

template<>
class CMyClass<empty, empty, empty> {
public:
    CMyClass(int) {} //do nothing
};

Now the compiler can find the right constructors and create your classes as you expect


Just to explain why lines like CMyClass<AddOn_A> A(100) work, it's because A (in that example) has only one parent, CMyClass<empty, empty, empty>, and your specialization

template<> class CMyClass< empty, empty, empty > {};

does have a default constructor, because it's empty (or, more formally, because it defines no other constructors). This breaks down immediately once you call CMyClass<AddOn_A, AddOn_B> AB(100) because that has two parents, CMyClass<AddOn_B, empty, empty> and CMyClass<empty, empty, empty>, however the former does not have a default constructor, so the compiler does not know how to construct it. That's why we must add that one line to the initializer list, so we tell the compiler to create CMyClass<AddOn_B, empty, empty> using its CMyClass(int x) constructor (note how that means the compiler will also try to make CMyClass<empty, empty, empty> with the x parameter, so we need to add a constructor to that specialization which will accept the parameter).

GRB
That works perfectly. Thanks for the explanation!
PaulH