tags:

views:

364

answers:

6

Is it possible to make a composite template class factory without manually specifying all of the combinations? What I mean is if I have these classes:

class CompositeBase {};

template< typename C1, typename C2, typename C3 >
class Composite : public CompositeBase
{
private:
    C1 component1;
    C2 component2;
    C3 component3;
};

class Component0 {}; //Also have Component1-9

I would like to create a function like this:

CompositeBase *CreateComposite(int c1, int c2, int c3);

so that

CreateComposite(4,3,7);

would create and return a

Composite<Component4,Component3,Component7>

The reason to do this is so that I can load data from a file and create the different composite objects. The file would have the three values for the components to create each composite object, then the other data required by it.

The problem is with 10 different components, there are 1000 different possible composite classes. To specify all of them would require a switch statement for the 1st component, 10 switch statements for the second component within that one, and 100 switch statements for the third component within those ten, and the function would be 1000+ lines long.

Is there any other way to write the CreateComposite function? Other than this:

CompositeBase *CreateComposite(int c1, int c2, int c3)
{
   switch(c1)
   {
   case 0:
       switch( c2 )
       {
       case 0:
           switch( c3 )
           {
              case 0: return new Composite<Component0,Component0,Component0>;
              case 1: return new Composite<Component0,Component0,Component1>;
              //etc
           }
           //etc
       }
       //etc
   }
}
A: 

Nope. Such would require reflection, which C++ does not have.

Why not just CreateComposite<T1, T2, T3>() ?

EDIT: Sounds like a case where the state/strategy pattern would be helpful.

Billy ONeal
I need to read this data from a file, so I can't call it that way.
Frank
+2  A: 

It is not possible to generate all the possible combinations of a template at runtime. The compiler needs to know which ones you will be using so that it can generate code for them.

Have you considered using composition rather than inheritance?

Edit: By composition I was thinking more along the lines of the following:


class Composite
{
private:
    ComponentBase * component1;
    ComponentBase * component2;
    ComponentBase * component3;
};
class Component0 : public ComponentBase {}; //Also have Component1-9

If you can't use pointers to a base class for some reason, you're stuck having code to generate all the permutations of template classes. But rather than using nested case statements, you can use a table to create new instances, and macros to make the code easier.

typedef CompositeBase * NewComposite();
#define NEW_COMPOSITE(P1,P2,P3) CompositeBase * NewComposite##P1##P2##P3() { return new Composite<Component##P1,Component##P2,Component##P3>; }
NEW_COMPOSITE(0,0,0) NEW_COMPOSITE(0,0,1) NEW_COMPOSITE(0,0,2) NEW_COMPOSITE(0,0,3)
NEW_COMPOSITE(0,0,4) ...
...
CompositeBase *CreateComposite(int c1, int c2, int c3)
{
    static NewComposite * newCompositeTable[10][10][10] = {{{NewComposite000,NewComposite001,...},{NewComposite010,...}}};
    return newCompositeTable[c1][c2][c3]();
}

I can't really say this is a better approach than the one you started with, but it's an alternate to consider.

Mark Ransom
I updated the post. I am using composition, at least I think so. It's just that the components of Composite are specified by the template arguments.
Frank
+2  A: 

Template arguments must be known at compile time. A possible solution would be: CreateComposite<int, int, int>() and you can specialize it for every possible case. Oh, well: that's rather a no go.. I'd suggest that you rather go with some oldfashioned dynamic polymorphism and a std::vector<ComponentBase>.

pmr
+ 1,000,000 for proposing the only rational solution, just use polymorphism!
Frank Krueger
Well, that's the thing. I do know all of the possible arguments at compile time, they can only be Component0-9. There will never be a Composite<int,float,double> for example. I added my example of a working, but lengthy CreateComposite function. It just seems like there should be some way to do it without all of that typing.I may have to do what you suggest, it just seemed that this was a nice way to create Composite without allocating the Components individually and without having to use virtual functions to access them.
Frank
You know the possible arguments at compile time, but not the -actual- arguments; they come in via your 3 integer parameters, at run time, which is too late to generate templates from.
Kylotan
@Kylotan That's why I said that you should specialize the factory for the possible combinations. This way requires a lot of code and is definitely not the right solution.
pmr
A: 

Whilst i agree with others that dynamic polymorphism is probably the way to go, technically you can do what you're asking. Everyone else, avert your eyes now... :

#include <stdexcept>
#include <iostream>

struct CompositeBase
{
    virtual ~CompositeBase () = 0 {}
    virtual std::ostream& output (std::ostream& os) const = 0;
};

template<typename CA, typename CB, typename CC>
struct Composite : public CompositeBase
{
    std::ostream& output (std::ostream& os) const
    {
        return os << componentA.id () << "," << componentB.id () << "," << componentC.id ();
    }

    CA componentA;
    CB componentB;
    CC componentC;
};

struct Component0 {int id () const {return 0;}};
struct Component1 {int id () const {return 1;}};
struct Component2 {int id () const {return 2;}};
struct Component3 {int id () const {return 3;}};
struct Component4 {int id () const {return 4;}};
// ...

template<int N> struct IntToType {};
template<> struct IntToType<0> {typedef Component0 Type;};
template<> struct IntToType<1> {typedef Component1 Type;};
template<> struct IntToType<2> {typedef Component2 Type;};
template<> struct IntToType<3> {typedef Component3 Type;};
template<> struct IntToType<4> {typedef Component4 Type;};
// ...

// Change 4 to match number of composites.
template<int N1 = 4, int N2 = 4, int N3 = 4>
struct CompositeFactory
{
    static CompositeBase* create (int c1, int c2, int c3)
    {
        if (c1 == N1)
        {
            if (c2 == N2)
            {
                if (c3 == N3)
                    return new Composite<IntToType<N1>::Type, IntToType<N2>::Type, IntToType<N3>::Type>;
                else
                    return CompositeFactory<N1, N2, N3-1>::create (c1, c2, c3);
            }
            else
                return CompositeFactory<N1, N2-1, N3>::create (c1, c2, c3);
        }
        else
            return CompositeFactory<N1-1, N2, N3>::create (c1, c2, c3);
    }
};

template<int N2, int N3>
struct CompositeFactory<-1, N2, N3>
{
    static CompositeBase* create (int c1, int c2, int c3)
    {
        throw std::runtime_error ("Could not create Composite");
    }
};

template<int N1, int N3>
struct CompositeFactory<N1, -1, N3>
{
    static CompositeBase* create (int c1, int c2, int c3)
    {
        throw std::runtime_error ("Could not create Composite");
    }
};

template<int N1, int N2>
struct CompositeFactory<N1, N2, -1>
{
    static CompositeBase* create (int c1, int c2, int c3)
    {
        throw std::runtime_error ("Could not create Composite");
    }
};

CompositeBase* createComposite (int c1, int c2, int c3)
{
    return CompositeFactory<>::create (c1, c2, c3);
}

int main (int argc, char* argv[])
{
    CompositeBase* comp = createComposite (4,1,2);

    comp->output (std::cout);

    return 0;
}

That works for 5 types and it should be obvious how to it extend to 10 types.

I'm not sure i would ever want to actually use this though - for 10 composites the compiler would generate all 1000 instantiations of the Composite template.

jon hanson
+1  A: 

You can avoid O(n^3) complexity to O(n) by using cascade template methods with single switch-case in it:

#include <iostream>
using namespace std;

class CompositeBase
{
public:
    virtual void print( std::ostream& o_out ) = 0;
};

template< typename C1, typename C2, typename C3 >
class Composite : public CompositeBase
{
public:
    void print( std::ostream& o_out )
    {
        o_out << typeid(*this).name();
    }
private:
    C1 component1;
    C2 component2;
    C3 component3;
};


class Component0 {};
class Component1 {};
class Component2 {};
class Component3 {};
class Component4 {};
class Component5 {};
class Component6 {};
class Component7 {};
class Component8 {};
class Component9 {};

template<typename C1,typename C2,typename C3>
CompositeBase *CreateComposite0()
{
    return new Composite<C1,C2,C3>();
}

template<typename C1,typename C2>
CompositeBase *CreateComposite1(int c3)
{
    switch(c3)
    {
    case 0: return CreateComposite0<C1,C2,Component0>();
    case 1: return CreateComposite0<C1,C2,Component1>();
    case 2: return CreateComposite0<C1,C2,Component2>();
    case 3: return CreateComposite0<C1,C2,Component3>();
    case 4: return CreateComposite0<C1,C2,Component4>();
    case 5: return CreateComposite0<C1,C2,Component5>();
    case 6: return CreateComposite0<C1,C2,Component6>();
    case 7: return CreateComposite0<C1,C2,Component7>();
    case 8: return CreateComposite0<C1,C2,Component8>();
    case 9: return CreateComposite0<C1,C2,Component9>();
    default: return 0;
    }
}

template<typename C1>
CompositeBase *CreateComposite2(int c2, int c3)
{
    switch(c2)
    {
    case 0: return CreateComposite1<C1,Component0>(c3);
    case 1: return CreateComposite1<C1,Component1>(c3);
    case 2: return CreateComposite1<C1,Component2>(c3);
    case 3: return CreateComposite1<C1,Component3>(c3);
    case 4: return CreateComposite1<C1,Component4>(c3);
    case 5: return CreateComposite1<C1,Component5>(c3);
    case 6: return CreateComposite1<C1,Component6>(c3);
    case 7: return CreateComposite1<C1,Component7>(c3);
    case 8: return CreateComposite1<C1,Component8>(c3);
    case 9: return CreateComposite1<C1,Component9>(c3);
    default: return 0;
    }
}

CompositeBase *CreateComposite(int c1,int c2, int c3)
{
    switch(c1)
    {
    case 0: return CreateComposite2<Component0>(c2,c3);
    case 1: return CreateComposite2<Component1>(c2,c3);
    case 2: return CreateComposite2<Component2>(c2,c3);
    case 3: return CreateComposite2<Component3>(c2,c3);
    case 4: return CreateComposite2<Component4>(c2,c3);
    case 5: return CreateComposite2<Component5>(c2,c3);
    case 6: return CreateComposite2<Component6>(c2,c3);
    case 7: return CreateComposite2<Component7>(c2,c3);
    case 8: return CreateComposite2<Component8>(c2,c3);
    case 9: return CreateComposite2<Component9>(c2,c3);
    default: return 0;
    }
}

int main()
{
    CompositeBase* base1 = CreateComposite(4,5,6);
    CompositeBase* base2 = CreateComposite(8,2,0);
    base1->print(cout);
    cout << endl;
    base2->print(cout);
    return 0;
}

Just for fun you can use boost proprocessor for O(1) complexity

#include <iostream>
#include <boost/preprocessor/repetition.hpp>
using namespace std;

class CompositeBase
{
public:
    virtual void print( std::ostream& o_out ) = 0;
};

template< typename C1, typename C2, typename C3 >
class Composite : public CompositeBase
{
public:
    void print( std::ostream& o_out )
    {
     o_out << typeid(*this).name();
    }
private:
    C1 component1;
    C2 component2;
    C3 component3;
};

#define DIM 10

#define COMPONENT_DECLARATION(z,n,unused) class BOOST_PP_CAT(Component,n) {};
BOOST_PP_REPEAT(DIM,COMPONENT_DECLARATION, ~)
#undef COMPONENT_DECLARATION

template<typename C1,typename C2,typename C3>
CompositeBase *CreateComposite0()
{
    return new Composite<C1,C2,C3>();
}

template<typename C1,typename C2>
CompositeBase *CreateComposite1(int c3)
{
#define COMPOSITE(z,n,unused) case n: return CreateComposite0<C1,C2,BOOST_PP_CAT(Component,n)>();
    switch(c3)
    {
BOOST_PP_REPEAT(DIM,COMPOSITE,~)
    default: return 0;
    }
#undef COMPOSITE
}

template<typename C1>
CompositeBase *CreateComposite2(int c2, int c3)
{
#define COMPOSITE(z,n,unused) case n: return CreateComposite1<C1,BOOST_PP_CAT(Component,n)>(c3);
    switch(c2)
    {
BOOST_PP_REPEAT(DIM,COMPOSITE,~)
    default: return 0;
    }
#undef COMPOSITE
}

CompositeBase *CreateComposite(int c1,int c2, int c3)
{
#define COMPOSITE(z,n,unused) case n: return CreateComposite2<BOOST_PP_CAT(Component,n)>(c2,c3);
    switch(c1)
    {
BOOST_PP_REPEAT(DIM,COMPOSITE,~)
    default: return 0;
    }
#undef COMPOSITE
}

#undef DIM

int main()
{
    CompositeBase* base1 = CreateComposite(4,5,6);
    CompositeBase* base2 = CreateComposite(8,2,0);
    base1->print(cout);
    cout << endl;
    base2->print(cout);
    return 0;
}

In any case, I'd recommend to avoid these solutions if possible.

Konstantin
A: 

Why would you even want a generic Composite class with 3 members of completely arbitrary types? I can't imagine that you can write any useful code for such a class in that situation. The Composite itself can't contain any useful code and any code that uses a composite will need to know what types are in it (which you're currently not storing anywhere).

However, if the 3 types each implement some sort of known interface - eg. the first is always a numeric type, the second is a sequence type, etc - then you can just use one factory for each type and use polymorphism to return something with the correct interface, and you don't need the templates at all.

Kylotan