How to avoid complex switches?

N

none

I have a class that takes a few template parameters:

template<typename A, typename B, typename C,typename D>
class MyClass {
// ...
};

The types A,B,C and D are selected from a user specified input file (properties file):


A = 1
B = 2
C = 1
D = 3


I then parse this file an need to create MyClass with the correct types:

void createMyClass (int a, int b, int c, int d) {


switch ( a ) {
case 1 :
typedef test::Green ColorType;
switch ( b ) {
case 1 :
typedef test::Water MediumType;
switch ( c ) {
case 1 :
typedef test::Linear InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
case 2 :
typedef test::Cubic InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
default :
}
break;
case 2 :
// ....


break;
default :
}
break;
case 2 :
typedef test::Blue ColorType;
switch ( b ) {
case 1 :
typedef test::Water MediumType;
switch ( c ) {
case 1 :
typedef test::Linear InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
case 2 :
typedef test::Cubic InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
default :
}
break;
case 2 :


break;
default :
}



break;
default :

}

}




But this switch grows extremely large when the number of choices for each type grows and is also
very ugly/error prone. It could be nice if it was possible to do something like this instead:




T getAType(int a) {
switch ( a ) {
case 1 :
typedef test::Green ColorType;
break;
case 2 :
typedef test::Blue ColorType;
break;
default :
}
return ColorType;
}




T getBType(int b) {
switch ( b ) {
case 1 :
typedef test::Water MediumType;
break;
case 2 :
typedef test::Rock MediumType;
break;
default :
}
return MediumType;
}


int main(){

typedef getAType(1) ColorType;
typedef getBType(2) MediumType;
...

MyClass<ColorType, MediumType, ...>
}


But this is not supported in C++. Any ideas on how to solve this combinatorics problem?
 
N

none

Leigh said:
I don't think you can really avoid a using switch for this type of thing
but type lists (Modern C++ Design) might make the switch code less ugly.
Personally I would just stick with the switch and perhaps use macros.
You have hit the age old compiler vs runtime problem, perhaps you would
be better off using a language which supports reflection? :)

/Leigh


Arg! Assume I have 4 variables which can have 4 values each then I need to hardcode 4â´ = 256 cases
in a monster switch!

This seems as a very basic type of functionality. Many programs gives the user an option of
selecting different values for a set of different variables.
 
N

none

Leigh said:
And for the pedants out there yes I know templates are a form of
polymorphism but you know very well that I am referring to subtyping.

/Leigh


I cannot change the design of the class MyClass<A,B,C,D> or the language so I guess its just getting
down with writing that monster switch :)
 
A

Alf P. Steinbach

* Leigh Johnston:
And for the pedants out there yes I know templates are a form of
polymorphism but you know very well that I am referring to subtyping.

You don't need to fear on that account.

In C++ "polymorphic class" refers to a class with at least one virtual member
function (e.g. in discussions of dynamic_cast).

It's much more difficult to convince novices that there is such a thing as
static polymorphism... ;-)


Cheers,

- Alf (pedant)

PS: It's amazing how revealing dicussions about terminology are. I'm guessing it
says something negative about me, but every time I read someone objecting to a
reasonable use of a term, as if there was only 1 meaning cast in stone and the
text should be grokkable by robots programmed only with that meaning, I'm
thinking "danger! moron attack!". And most often that turns out to be the case,
for those who are less intellectually challenged have other things to discuss...
 
I

Ian Collins

I have a class that takes a few template parameters:

template<typename A, typename B, typename C,typename D>
class MyClass {
// ...
};

The types A,B,C and D are selected from a user specified input file
(properties file):


A = 1
B = 2
C = 1
D = 3


I then parse this file an need to create MyClass with the correct types:
But this switch grows extremely large when the number of choices for
each type grows and is also very ugly/error prone. It could be nice if
it was possible to do something like this instead:

Could you use the factory pattern? If you create a polymorphic base for
MyClass, you can have simple factory objects:

struct MyClassFactoryBase
{
virtual MyClassBase* build() = 0
};

template<typename A, typename B, typename C,typename D>
struct MyClassFactory : MyClassFactoryBase
{
MyClass<A,B,C,D>* build() { ... }
};

You can yen populate a lookup table with MyClassFactory objects.
 
A

Alf P. Steinbach

* none:
I have a class that takes a few template parameters:

template<typename A, typename B, typename C,typename D>
class MyClass {
// ...
};

The types A,B,C and D are selected from a user specified input file
(properties file):


A = 1
B = 2
C = 1
D = 3


I then parse this file an need to create MyClass with the correct types:
[snip]

But this is not supported in C++. Any ideas on how to solve this
combinatorics problem?

Well, the best would be to do as Leigh Johnston has suggested else-thread, to
use run time polymorphism. Also to replace your use of types, with plain values.

But it's an interesting problem.

The monkey solution below just cries out for some elegant generalization...

Oh my, looking at it now I've forgotten to define default constructors.

I wonder how this code (the 'static const' bits) passed compilation, but, it did.


<code>
#include <iostream>
#include <typeinfo>

template<typename A, typename B, typename C,typename D>
class MyClass
{
// ...
};

template< typename T >
void foo( T const& )
{
std::cout << typeid( T ).name() << std::endl;
}

class A1{}; class A2{}; class A3{}; class A4{};
class B1{}; class B2{}; class B3{}; class B4{};
class C1{}; class C2{}; class C3{}; class C4{};
class D1{}; class D2{}; class D3{}; class D4{};

struct IBoundD
{
virtual void instantiate() const = 0;
};

template< typename A, typename B, typename C, typename D >
struct BoundD: IBoundD
{
virtual void instantiate() const
{
// For example, whatever.
foo( MyClass<A, B, C, D>() );
}
};

struct IBoundC
{
virtual IBoundD const& bindD( int const d ) const = 0;
};

template< typename A, typename B, typename C >
struct BoundC: IBoundC
{
virtual IBoundD const& bindD( int const d ) const
{
static const BoundD<A, B, C, D1> d1;
static const BoundD<A, B, C, D2> d2;
static const BoundD<A, B, C, D3> d3;
static const BoundD<A, B, C, D4> d4;

switch( d )
{
case 1: return d1;
case 2: return d2;
case 3: return d3;
case 4: return d4;
}
assert( false );
}
};

struct IBoundB
{
virtual IBoundC const& bindC( int const b ) const = 0;
};

template< typename A, typename B >
struct BoundB: IBoundB
{
virtual IBoundC const& bindC( int const c ) const
{
static const BoundC<A, B, C1> c1;
static const BoundC<A, B, C2> c2;
static const BoundC<A, B, C3> c3;
static const BoundC<A, B, C4> c4;

switch( c )
{
case 1: return c1;
case 2: return c2;
case 3: return c3;
case 4: return c4;
}
assert( false );
}
};

struct IBoundA
{
virtual IBoundB const& bindB( int const b ) const = 0;
};

template< typename A >
struct BoundA: IBoundA
{
virtual IBoundB const& bindB( int const b ) const
{
static const BoundB<A, B1> b1;
static const BoundB<A, B2> b2;
static const BoundB<A, B3> b3;
static const BoundB<A, B4> b4;

switch( b )
{
case 1: return b1;
case 2: return b2;
case 3: return b3;
case 4: return b4;
}
assert( false );
}
};

struct Instantiator
{
IBoundA const& bindA( int const a ) const
{
static const BoundA<A1> a1;
static const BoundA<A2> a2;
static const BoundA<A3> a3;
static const BoundA<A4> a4;

switch( a )
{
case 1: return a1;
case 2: return a2;
case 3: return a3;
case 4: return a4;
}
assert( false );
}
};

int main()
{
int a = 1;
int b = 2;
int c = 1;
int d = 3;

Instantiator()
.bindA( a )
.bindB( b )
.bindC( c )
.bindD( d )
.instantiate();
}
</code>



Cheers & hth.,

- Alf
 
N

none

Ian said:
Could you use the factory pattern? If you create a polymorphic base for
MyClass, you can have simple factory objects:

struct MyClassFactoryBase
{
virtual MyClassBase* build() = 0
};

template<typename A, typename B, typename C,typename D>
struct MyClassFactory : MyClassFactoryBase
{
MyClass<A,B,C,D>* build() { ... }
};

You can yen populate a lookup table with MyClassFactory objects.


But I still need to parse the user specified selection into to the correct types, so I don't see how
you can avoid a switch with the above?
 
P

Pavel

none said:
I have a class that takes a few template parameters:

template<typename A, typename B, typename C,typename D>
class MyClass {
// ...
};

The types A,B,C and D are selected from a user specified input file
(properties file):


A = 1
B = 2
C = 1
D = 3


I then parse this file an need to create MyClass with the correct types:

void createMyClass (int a, int b, int c, int d) {


switch ( a ) {
case 1 :
typedef test::Green ColorType;
switch ( b ) {
case 1 :
typedef test::Water MediumType;
switch ( c ) {
case 1 :
typedef test::Linear InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
case 2 :
typedef test::Cubic InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
default :
}
break;
case 2 :
// ....


break;
default :
}
break;
case 2 :
typedef test::Blue ColorType;
switch ( b ) {
case 1 :
typedef test::Water MediumType;
switch ( c ) {
case 1 :
typedef test::Linear InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
case 2 :
typedef test::Cubic InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
default :
}
break;
case 2 :


break;
default :
}



break;
default :

}

}




But this switch grows extremely large when the number of choices for
each type grows and is also very ugly/error prone. It could be nice if
it was possible to do something like this instead:




T getAType(int a) {
switch ( a ) {
case 1 :
typedef test::Green ColorType;
break;
case 2 :
typedef test::Blue ColorType;
break;
default :
}
return ColorType;
}




T getBType(int b) {
switch ( b ) {
case 1 :
typedef test::Water MediumType;
break;
case 2 :
typedef test::Rock MediumType;
break;
default :
}
return MediumType;
}


int main(){

typedef getAType(1) ColorType;
typedef getBType(2) MediumType;
...

MyClass<ColorType, MediumType, ...>
}


But this is not supported in C++. Any ideas on how to solve this
combinatorics problem?

Calls for a dynamic call of the compiler :).

BTW, in Java, it's now an official part of the platform but it have had
it for years (Janino & stuff).

Hey, even IBM's FORTRAN IV had it..

Maybe in the next version of the Standard after C++x0 issued after I
will have retired or died, C++ will agree on a standard way of walking
directory tree and in the version after that -- the standard way of
calling compiler dynamically...

Seriously,

1. I am confused as of why you have 4 template parameters in the
template class definition but only 3 arguments specified in the code
fragments.

2. What do you do with that myClass after you create it? Apparently,
each of those variables have a different scope, so all operations on
these should be coded separately, too? Is this the intention? Otherwise,
please could you show some more of the intended use.

3. Assuming there are 4 parameters and 3 possible values for every of A,
B, C, D, you need to have pow(3, 4) == 729 classes *instantiated* to be
able to account for every combination. This probably will not scale well
so maybe you shouldn't use that "MyClass" template at all. It might be
useful to imagine you don't have that "MyClass" template; how would you
go about solving the problem then?

4. If you absolutely need to use the class, consider generating the code
with some tool like CodeSmith (or m4 if you are adventurous or already
know it).


-Pavel
 
P

Pavel

Alf said:
* Pavel:

Happily, no. You might check my code else-thread.


Cheers & hth.,

- Alf
Why, I saw your code. It is smart, it reduces the amount of code to
write but it still instantiates all possible classes.

I added a static variable into a function in your BoundD template; the
code size increased by 256 times of the variable size. With dynamic
compilation, only one class of those 256 would be instantiated. And,
with anything but basic types as potential template arguments you can be
sure the number 4 will grow to 5, 6 etc.. making it 1296; then to 10,
making it 10000. An idea of generating all classes of the combinatorial
expansion when only one is needed sends shudders down my spine...

-Pavel
 
I

Ian Collins

But I still need to parse the user specified selection into to the
correct types, so I don't see how you can avoid a switch with the above?

By populating the lookup table with the appropriate factory objects.

Something like (reducing the dimensions to 2 for simplicity):

std::vector< std::vector<MyClassFactoryBase*> > lookup(2);

void populateLookup()
{
lookup[0].resize(2);
lookup[0][0] = new MyClassFactory<A,B,C,D>;
lookup[0][1] = new MyClassFactory<D,A,B,C>;
lookup[1].resize(2);
lookup[1][0] = new MyClassFactory<C,D,A,B>;
lookup[1][1] = new MyClassFactory<B,C,D,A>;
}

MyClassBase* createMyClass( int a, int b )
{
return lookup[a]->build();
}
 
S

Stefan Ram

Eric J. Holtman said:
Learn to trim your posts.

Thank you! Finally, someone says this. All those long quotes
are really unpleasant.

The OP seems to boil down to the question of whether one can
write

if( i ){ c<a>x; }else{ c<b>x; }

in a manner similar to

c<i?a:b> x;

. (If the construction of an instance of the given classes
has no effect, both statements are of course equivalent to
the empty statement.)

The best way to put the question would be a more high-level
description of what the program actually should achieve, so
that one then can think about a good way to implement this
using the given means of the C++ language.
 
S

Stefan Ram

c<i?a:b> x;

And this already shows the dilemma: template initialization
happens /at compile time/, while i is only known as late as
/at run time/. As others already have said, what is possible is:

c x(i);

, but whether this is adequate can only be told after more
is known about the overall task to be accomplished.

Object-oriented programming is about choosing implementations
as late as possible, while templates are about choosing them
as early as possible. One has to make up one's mind about
which of these two one wants or needs under given requirements.
 
J

James Kanze

I have a class that takes a few template parameters:
template<typename A, typename B, typename C,typename D>
class MyClass {
// ...

The types A,B,C and D are selected from a user specified input file
(properties file):
A = 1
B = 2
C = 1
D = 3
I then parse this file an need to create MyClass with the correct types:
void createMyClass (int a, int b, int c, int d) {
switch ( a ) {
case 1 :
typedef test::Green ColorType;
switch ( b ) {
case 1 :
typedef test::Water MediumType;
switch ( c ) {
case 1 :
typedef test::Linear InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
case 2 :
typedef test::Cubic InterpolationType;
MyClass<ColorTyper, MediumType, InterpolationType > myClass;
break;
default :
}
break;

I suspect that this is more a results of some trimming you've
done for posting, but that last switch is neither legal (since
case doesn't create a scope, you have multiple definitions) nor
useful (since as soon as you declare the variable myClass, you
quit the scope, and it ceases to exist or be accessible.

For the rest... others have already pointed out that you need
to instantiate all of the possible template code. If that's
acceptable, then something simple along the lines of

struct TypeId
{
int a;
int b;
int c;
int d;
};
std::map<TypeId, Factory const*> factory_map;

would do the trick (with all of the obviously necessary
additional functions, an operator< for TypeId, etc.)

Otherwise, one solution that I've occasionally found useful for
this sort of thing is to place each template instantiation in a
separate DLL, and load only one of the DLL's according to the
data found in configuration file. Usually, it's simpler to just
instantiate everything in the main program, but if the set of
possible types is open (someone might add one in the future),
the DLL solution adapts more easily, and if the instantiations
are really, really big, and your program needs all the memory it
can get, the DLL solution should reduce the memory footprint of
the code.
 
T

tonydee

I have a class that takes a few template parameters:

template<typename A, typename B, typename C,typename D>
class MyClass {
// ...
};

The types A,B,C and D are selected from a user specified input file (properties file):

A = 1
B = 2
C = 1
D = 3

I then parse this file an need to create MyClass with the correct types:

void createMyClass (int a, int b, int c, int d) {

     switch ( a ) {
       case 1 :
         typedef test::Green ColorType;
         switch ( b ) {
           case 1 :
             typedef test::Water MediumType;
             switch ( c ) {
               case 1 :
                 typedef test::Linear InterpolationType;
                 MyClass<ColorTyper, MediumType, InterpolationType > myClass;
                 break;
...

Any ideas on how to solve this combinatorics problem?

You might be able to apply the boost preprocessor library. I've
illustrated the concepts below....

Cheers,
Tony

#include <cstdlib>
#include <iostream>

#include <boost/preprocessor/repetition.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>

enum A
{
a0, a1, a2, a3
};

#define NUM_A 4

enum B
{
b0, b1, b2, b3, b4
};

#define NUM_B 5

template <A a, B b>
void fn()
{
std::cout << "A " << a << ", B " << b << '\n';
}

#define CASE_B(z, n, a) \
case n: fn<A(a), B(n)>(); break; \

#define CASE_A(z, n, unused) \
case n: \
switch (b) { \
BOOST_PP_REPEAT(NUM_B, CASE_B, n) \
} \
break;

int main(int argc, char* argv[])
{
if (argc != 3)
{
std::cerr << "usage: rt2ct <a> <b>\n";
return EXIT_FAILURE;
}

int a = atoi(argv[1]);
int b = atoi(argv[2]);

switch (a)
{
BOOST_PP_REPEAT(NUM_A, CASE_A, ignored)
}
}
 
S

Stuart Redmann

I have a class that takes a few template parameters:

template<typename A, typename B, typename C,typename D>
class MyClass {
// ...

};

The types A,B,C and D are selected from a user specified input file (properties file):

A = 1
B = 2
C = 1
D = 3

I then parse this file an need to create MyClass with the correct types:

void createMyClass (int a, int b, int c, int d) {

     switch ( a ) {
       case 1 :
         typedef test::Green ColorType;
         switch ( b ) {
           case 1 :
             typedef test::Water MediumType;
             switch ( c ) {
               case 1 :
                 typedef test::Linear InterpolationType;
                 MyClass<ColorTyper, MediumType, InterpolationType > myClass;
                 break;
               case 2 :
                 typedef test::Cubic InterpolationType;
                 MyClass<ColorTyper, MediumType, InterpolationType > myClass;
                 break;
               default :
             }
             break;
           case 2 :
             // ....

             break;
           default :
         }
         break;
       case 2 :
         typedef test::Blue ColorType;
         switch ( b ) {
           case 1 :
             typedef test::Water MediumType;
             switch ( c ) {
               case 1 :
                 typedef test::Linear InterpolationType;
                 MyClass<ColorTyper, MediumType, InterpolationType > myClass;
                 break;
               case 2 :
                 typedef test::Cubic InterpolationType;
                 MyClass<ColorTyper, MediumType, InterpolationType > myClass;
                 break;
               default :
             }
             break;
           case 2 :

             break;
           default :
         }

         break;
       default :

     }

}

But this switch grows extremely large when the number of choices for each type grows and is also
very ugly/error prone.

[snip]

As far as I can see in the other postings of this thread, you can
hardly get around typing an expontentially growing amount of code as
your choices increase, even if you use those fancy creator patterns
and whatnot. Since using (run-time) polymorphism is out of the
question for you, you may think about writing a small script that
simply generates your switch statement (as yacc and flex do).

Regards,
Stuart
 
A

Alf P. Steinbach

* Stuart Redmann:
I have a class that takes a few template parameters:
[snip]

As far as I can see in the other postings of this thread, you can
hardly get around typing an expontentially growing amount of code as
your choices increase, even if you use those fancy creator patterns
and whatnot.

The amount of code to type is linear in the number of values of the types. I
posted code showing how to do that. As noted in that post, and before that by
others, it doesn't mean the template approach is a good idea.

Cheers & hth.,

- Alf
 
J

James Kanze

* Stuart Redmann:
I have a class that takes a few template parameters:
[snip]
As far as I can see in the other postings of this thread,
you can hardly get around typing an expontentially growing
amount of code as your choices increase, even if you use
those fancy creator patterns and whatnot.
The amount of code to type is linear in the number of values
of the types. I posted code showing how to do that. As noted
in that post, and before that by others, it doesn't mean the
template approach is a good idea.

The amount of code to type is constant, since he'll obviously
write a small script to generate all of the template
instantiations.:)
 
T

tonydee

The amount of code to type is linear in the number of values of the types.. I
posted code showing how to do that. As noted in that post, and before that by
others, it doesn't mean the template approach is a good idea.

True that we don't know enough about the specific intended use that
spawned the thread, let alone profiling results, to know whether the
template approach is justified. Still, in a few cases it's indeed the
best thing to do, so it's an important technique. It's conceptually
similar to the Factory pattern: same basic problem of switching to
code, whether it instantiates a particular template or constructs a
particular derived class....

Cheers,
Tony
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,772
Messages
2,569,593
Members
45,111
Latest member
KetoBurn
Top