maximum cases in a switch ?

L

Lynn McGuire

Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

Thanks,
Lynn
 
L

LR

Lynn said:
Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

Thanks,
Lynn

According to n3242,

"Case labels for a switch statement (excluding those for any nested
switch statements) [16 384]."
 
J

Juha Nieminen

Lynn McGuire said:
Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

I can't find any stated upper limit in the standard. There probably
isn't (except for the maximum value of the numerical type being used,
of course). Individual compilers might impose a limit based on the
memory usage that parsing a switch statement requires, but my guess
is that this limit is probably very large.
 
L

Lynn McGuire

Lynn said:
Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

Thanks,
Lynn

According to n3242,

"Case labels for a switch statement (excluding those for any nested
switch statements) [16 384]."

A short int, cool ! Gives me plenty of room
for additions.

Thanks,
Lynn
 
V

Victor Bazarov

Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

You have your answer, I'm not going to repeat it. Remember that it's
not normative, though.

I have a different question. How easy do you think it is going to be to
maintain the code with more than 1000 cases in a single switch
statement? How easy is it to debug? Wouldn't it make sense, perhaps,
to categorize your values and call different functions based on, say,
ranges first and do further branching later in those functions, maybe?
OK, OK, not /a/ question, but three.

And a slightly different approach would be to implement handling of
those ranges in an external module (dynamically loaded library, or
"shared object" as they are known in another unixverse) which will
register the range (or ranges) of values which it serves with its
function, pointers to which you should keep while the module is
loaded... Just thought I'd suggest that, maybe you find it useful.

V
 
L

Lynn McGuire

Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

You have your answer, I'm not going to repeat it. Remember that it's not normative, though.

I have a different question. How easy do you think it is going to be to maintain the code with more than 1000 cases in a single
switch statement? How easy is it to debug? Wouldn't it make sense, perhaps, to categorize your values and call different functions
based on, say, ranges first and do further branching later in those functions, maybe? OK, OK, not /a/ question, but three.

And a slightly different approach would be to implement handling of those ranges in an external module (dynamically loaded library,
or "shared object" as they are known in another unixverse) which will register the range (or ranges) of values which it serves with
its function, pointers to which you should keep while the module is loaded... Just thought I'd suggest that, maybe you find it
useful.

V


Incredibly easy. Here is some of the method:

DataDescriptor aDD_GEN_CALVAPPRE (GEN_CALVAPPRE, "CALVAPPRE",
"Calculate Vapor Pressure for all streams", true, false, false,
SYM_Enumerated, nil, nil, nil, nil, nil, SYM_BOOLEAN,
SYM_DGenInit, 420, nil, nil);

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
switch (aSymbol)
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;
....
}

// default return
return NULL;
}

This is a 470,000 line C++ Win32 user interface for a highly
successful calculation engine. This code is bulletproof and
very extensible. This particular code was actually converted
from Smalltalk to C++ about 10 years ago.

Lynn
 
L

Leo Equinox Gaspard

Incredibly easy. Here is some of the method:
DataDescriptor aDD_GEN_CALVAPPRE (GEN_CALVAPPRE, "CALVAPPRE",
"Calculate Vapor Pressure for all streams", true, false, false,
SYM_Enumerated, nil, nil, nil, nil, nil, SYM_BOOLEAN,
SYM_DGenInit, 420, nil, nil);

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
switch (aSymbol)
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;
...
}

// default return
return NULL;
}

This is a 470,000 line C++ Win32 user interface for a highly
successful calculation engine. This code is bulletproof and
very extensible. This particular code was actually converted
from Smalltalk to C++ about 10 years ago.

Lynn

Oh my God.

You are laughing, isn't it ?

There is nothing about anything named calvappre on the internet.
 
L

Lynn McGuire

Oh my God.

You are laughing, isn't it ?

There is nothing about anything named calvappre on the internet.

Try googling "cal vap pre". That is shorthand
in our internal engineering software language
for a specific calculation.

Lynn
 
V

Victor Bazarov

Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

You have your answer, I'm not going to repeat it. Remember that it's
not normative, though.

I have a different question. How easy do you think it is going to be
to maintain the code with more than 1000 cases in a single
switch statement? How easy is it to debug? Wouldn't it make sense,
perhaps, to categorize your values and call different functions
based on, say, ranges first and do further branching later in those
functions, maybe? OK, OK, not /a/ question, but three.

And a slightly different approach would be to implement handling of
those ranges in an external module (dynamically loaded library,
or "shared object" as they are known in another unixverse) which will
register the range (or ranges) of values which it serves with
its function, pointers to which you should keep while the module is
loaded... Just thought I'd suggest that, maybe you find it
useful.

V


Incredibly easy. Here is some of the method:

DataDescriptor aDD_GEN_CALVAPPRE (GEN_CALVAPPRE, "CALVAPPRE",
"Calculate Vapor Pressure for all streams", true, false, false,
SYM_Enumerated, nil, nil, nil, nil, nil, SYM_BOOLEAN,
SYM_DGenInit, 420, nil, nil);

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
switch (aSymbol)
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;


Ugh! You can do it with a macro

#define returnDDfor(A) case A: return & aDD_ ## A

and

returnDDfor(GEN_CALVAPPRE);
returnDDfor(GEN_CALSONVEL);

At least you need to type it only once...

I wonder, have you tried looking at the generated code? Does the
compiler have a jump table or does it create a bunch of 'if-then goto'
parts? The latter gets expensive for the case labels at the bottom of
the switch statement.
...
}

// default return
return NULL;
}

This is a 470,000 line C++ Win32 user interface for a highly
successful calculation engine. This code is bulletproof and
very extensible. This particular code was actually converted
from Smalltalk to C++ about 10 years ago.

Lynn

V
 
J

Juha Nieminen

Victor Bazarov said:
I wonder, have you tried looking at the generated code? Does the
compiler have a jump table or does it create a bunch of 'if-then goto'
parts? The latter gets expensive for the case labels at the bottom of
the switch statement.

One can generally assume that modern compilers are decently good at
optimizing large switch blocks. (If a specific compiler isn't, chances
are that it isn't good at optimizing anything else either.)
 
M

Marcel Müller

Lynn said:
Incredibly easy. Here is some of the method:

DataDescriptor aDD_GEN_CALVAPPRE (GEN_CALVAPPRE, "CALVAPPRE",
"Calculate Vapor Pressure for all streams", true, false,
false,
SYM_Enumerated, nil, nil, nil, nil, nil, SYM_BOOLEAN,
SYM_DGenInit, 420, nil, nil);

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
switch (aSymbol)
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;
...
}

// default return
return NULL;
}

I would strongly recommend a meta data table.

static const DataDescriptor DispatchTable[] =
{ DataDescriptor(GEN_CALVAPPRE, "CALVAPPRE",
"Calculate Vapor Pressure for all streams", true, false, false,
SYM_Enumerated, nil, nil, nil, nil, nil, SYM_BOOLEAN,
SYM_DGenInit, 420, nil, nil),
DataDescriptor(GEN_CALSONVEL, ...),
...
};

If the DataDescriptors are ordered by the key aSymbol, you could use a
binary search to locate a DataDescriptor in the table.

Alternatively class DataDescriptor could hold a static repository of
type std::map<int,DataDescriptor*> with all it's instances. The
constructor of DataDescriptor could register it's own instance in the
repository. Then the method GenGroup::descriptor reduces to a hash
lookup in the repository. No more than a dozen code lines instead of
some thousands in the switch case. And the advantage is that you can
keep the code for the DataDescriptor definitions without changes. You
only have to delete the large switch.
This is a 470,000 line C++ Win32 user interface for a highly
successful calculation engine.

Maybe, but keep in mind that compilers are not well tested with this
kind of code. I triggered internal compiler errors with too large
functions from time to time (large window procedures).
This code is bulletproof and
very extensible.

The two above solutions too.


Marcel
 
J

Juha Nieminen

Marcel Müller said:
If the DataDescriptors are ordered by the key aSymbol, you could use a
binary search to locate a DataDescriptor in the table.

Since he's clearly aiming for maximum speed, the switch block is
probably the most efficient solution because most compilers will create
a jump table, which means executing the proper case block will be done
in O(1) (rather than in O(log n) as with the binary search).

(Now, of course it's not guaranteed that all compilers will be so smart
as to generate a jump table from a large switch block, but AFAIK most
modern compilers do.)

Of course an alternative solution would be to create the jump table
explicitly yourself (basically, an array of function pointers which you
index with the value). This would allow a better rearrangment of the
code into their own functions, but I don't know if it would result in
a more, less or equally efficient program. (This solution, however, has
the disadvantage of making it more complicated for the "case blocks", in
this case the separate functions, to handle shared local variables. Passing
them as parameters to the functions might decrease efficiency.)
 
J

Jorgen Grahn

One can generally assume that modern compilers are decently good at
optimizing large switch blocks. (If a specific compiler isn't, chances
are that it isn't good at optimizing anything else either.)

With such an unusually large switch, I'd rather not assume anything.
The price for being wrong could be high, like Victor said.

I'd be interested to hear what the compiler generates in this case.

/Jorgen
 
J

Joe Greer

Lynn McGuire said:
Lynn said:
Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

Thanks,
Lynn

According to n3242,

"Case labels for a switch statement (excluding those for any nested
switch statements) [16 384]."

A short int, cool ! Gives me plenty of room
for additions.

Thanks,
Lynn

Have you considered just creating an array of your return values and
using your symbol as it's index? It would seem to be quicker and more
straight forward than trying to have a huge switch statement. For
example:

static DataDescriptor * myDataDescriptors[] =
{
&aDD_GEN_CALVAPPRE,
&aDD_GEN_CALSONVEL,
&aDD_GEN_ONLYWRITESTREAM,
..
..
..
}

DataDescriptor * GenGroup::descriptor(int aSymbol, int version)
{
// do some validating of aSymbol here, can possibly add or subtract
an offset etc.
return myDataDescriptors[aSymbol];
}

joe
 
V

Victor Bazarov

Since he's clearly aiming for maximum speed, the switch block is
probably the most efficient solution because most compilers will create
a jump table, which means executing the proper case block will be done
in O(1) (rather than in O(log n) as with the binary search).

(Now, of course it's not guaranteed that all compilers will be so smart
as to generate a jump table from a large switch block, but AFAIK most
modern compilers do.)

Of course an alternative solution would be to create the jump table
explicitly yourself (basically, an array of function pointers which you
index with the value). This would allow a better rearrangment of the
code into their own functions, but I don't know if it would result in
a more, less or equally efficient program. (This solution, however, has
the disadvantage of making it more complicated for the "case blocks", in
this case the separate functions, to handle shared local variables. Passing
them as parameters to the functions might decrease efficiency.)

Not if you *assume* those functions are inlined, like "most modern
compilers do"...

V
 
V

Victor Bazarov

Lynn McGuire said:
Lynn McGuire wrote:
Is there a maximum number of cases in a switch ?
I have a class with a method that now has 525
cases in a switch and will probably double this
over the next couple of years.

Thanks,
Lynn

According to n3242,

"Case labels for a switch statement (excluding those for any nested
switch statements) [16 384]."

A short int, cool ! Gives me plenty of room
for additions.

Thanks,
Lynn

Have you considered just creating an array of your return values and
using your symbol as it's index? It would seem to be quicker and more
straight forward than trying to have a huge switch statement. For
example:

static DataDescriptor * myDataDescriptors[] =
{
&aDD_GEN_CALVAPPRE,
&aDD_GEN_CALSONVEL,
&aDD_GEN_ONLYWRITESTREAM,
.
.
.
}
;

And this would only work if the values on which you index the array
('GEN_CALVAPPRE', 'GEN_CALSONVEL', etc) are actually ordinal numbers,
and their order does not change (not that I'm suggesting that it will do
so often, but still). Otherwise, it's a lookup table, which, granted,
needs to only be initialized once, and then used all the time, and it
will only be faster if it's a dense array. As soon as it's a sparse
array, you either waste memory, or you need to use std::map (or
'unordered_map') and the time for looking values up is not necessarily
better...
DataDescriptor * GenGroup::descriptor(int aSymbol, int version)
{
// do some validating of aSymbol here, can possibly add or subtract
an offset etc.
return myDataDescriptors[aSymbol];
}

joe

V
 
L

Lynn McGuire

Lynn said:
Incredibly easy. Here is some of the method:

DataDescriptor aDD_GEN_CALVAPPRE (GEN_CALVAPPRE, "CALVAPPRE",
"Calculate Vapor Pressure for all streams", true, false, false,
SYM_Enumerated, nil, nil, nil, nil, nil, SYM_BOOLEAN,
SYM_DGenInit, 420, nil, nil);

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
switch (aSymbol)
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;
...
}

// default return
return NULL;
}

I would strongly recommend a meta data table.

static const DataDescriptor DispatchTable[] =
{ DataDescriptor(GEN_CALVAPPRE, "CALVAPPRE",
"Calculate Vapor Pressure for all streams", true, false, false,
SYM_Enumerated, nil, nil, nil, nil, nil, SYM_BOOLEAN,
SYM_DGenInit, 420, nil, nil),
DataDescriptor(GEN_CALSONVEL, ...),
...
};

If the DataDescriptors are ordered by the key aSymbol, you could use a binary search to locate a DataDescriptor in the table.

Alternatively class DataDescriptor could hold a static repository of type std::map<int,DataDescriptor*> with all it's instances. The
constructor of DataDescriptor could register it's own instance in the repository. Then the method GenGroup::descriptor reduces to a
hash lookup in the repository. No more than a dozen code lines instead of some thousands in the switch case. And the advantage is
that you can keep the code for the DataDescriptor definitions without changes. You only have to delete the large switch.
This is a 470,000 line C++ Win32 user interface for a highly
successful calculation engine.

Maybe, but keep in mind that compilers are not well tested with this kind of code. I triggered internal compiler errors with too
large functions from time to time (large window procedures).
This code is bulletproof and
very extensible.

The two above solutions too.


Marcel

This solution is order dependent. Mine is not. Not that
I'm looking for a new solution.

Lynn
 
L

Lynn McGuire

With such an unusually large switch, I'd rather not assume anything.
The price for being wrong could be high, like Victor said.

I'd be interested to hear what the compiler generates in this case.

/Jorgen

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
0075D5B0 push ebp
0075D5B1 mov ebp,esp
0075D5B3 sub esp,8
0075D5B6 mov dword ptr [ebp-4],ecx
switch (aSymbol)
0075D5B9 mov eax,dword ptr [aSymbol]
0075D5BC mov dword ptr [ebp-8],eax
0075D5BF cmp dword ptr [ebp-8],20Ch
0075D5C6 ja $LN1+7 (75EA22h)
0075D5CC mov ecx,dword ptr [ebp-8]
0075D5CF jmp dword ptr (75EA2Ch)[ecx*4]
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
0075D5D6 mov eax,offset aDD_GEN_CALVAPPRE (0DA0080h)
0075D5DB jmp $LN1+9 (75EA24h)
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
0075D5E0 mov eax,offset aDD_GEN_CALSONVEL (0D88908h)
0075D5E5 jmp $LN1+9 (75EA24h)
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;
0075D5EA mov eax,offset aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET (0D9C648h)
0075D5EF jmp $LN1+9 (75EA24h)
....

Lynn
 
P

Paul Brettschneider

Lynn said:
With such an unusually large switch, I'd rather not assume anything.
The price for being wrong could be high, like Victor said.

I'd be interested to hear what the compiler generates in this case.

/Jorgen

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
0075D5B0 push ebp
0075D5B1 mov ebp,esp
0075D5B3 sub esp,8
0075D5B6 mov dword ptr [ebp-4],ecx
switch (aSymbol)
0075D5B9 mov eax,dword ptr [aSymbol]
0075D5BC mov dword ptr [ebp-8],eax
0075D5BF cmp dword ptr [ebp-8],20Ch
0075D5C6 ja $LN1+7 (75EA22h)
0075D5CC mov ecx,dword ptr [ebp-8]
0075D5CF jmp dword ptr (75EA2Ch)[ecx*4]
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
0075D5D6 mov eax,offset aDD_GEN_CALVAPPRE (0DA0080h)
0075D5DB jmp $LN1+9 (75EA24h)
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
0075D5E0 mov eax,offset aDD_GEN_CALSONVEL (0D88908h)
0075D5E5 jmp $LN1+9 (75EA24h)
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;
0075D5EA mov eax,offset aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET
(0D9C648h)
0075D5EF jmp $LN1+9 (75EA24h)

Silly compiler. It should store the return value directly in the table and
avoid the unnecessary jump. ;)
 
P

Paul Brettschneider

Paul said:
Lynn said:
On Wed, 2011-08-10, Juha Nieminen wrote:
I wonder, have you tried looking at the generated code? Does the
compiler have a jump table or does it create a bunch of 'if-then goto'
parts? The latter gets expensive for the case labels at the bottom of
the switch statement.

One can generally assume that modern compilers are decently good at
optimizing large switch blocks. (If a specific compiler isn't, chances
are that it isn't good at optimizing anything else either.)

With such an unusually large switch, I'd rather not assume anything.
The price for being wrong could be high, like Victor said.

I'd be interested to hear what the compiler generates in this case.

/Jorgen

DataDescriptor * GenGroup::descriptor (int aSymbol, int version)
{
0075D5B0 push ebp
0075D5B1 mov ebp,esp
0075D5B3 sub esp,8
0075D5B6 mov dword ptr [ebp-4],ecx
switch (aSymbol)
0075D5B9 mov eax,dword ptr [aSymbol]
0075D5BC mov dword ptr [ebp-8],eax
0075D5BF cmp dword ptr [ebp-8],20Ch
0075D5C6 ja $LN1+7 (75EA22h)
0075D5CC mov ecx,dword ptr [ebp-8]
0075D5CF jmp dword ptr (75EA2Ch)[ecx*4]
{
case GEN_CALVAPPRE:
return & aDD_GEN_CALVAPPRE;
0075D5D6 mov eax,offset aDD_GEN_CALVAPPRE (0DA0080h)
0075D5DB jmp $LN1+9 (75EA24h)
case GEN_CALSONVEL:
return & aDD_GEN_CALSONVEL;
0075D5E0 mov eax,offset aDD_GEN_CALSONVEL (0D88908h)
0075D5E5 jmp $LN1+9 (75EA24h)
case GEN_ONLYWRITESTREAMBOXONFIRSTSHEET:
return & aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET;
0075D5EA mov eax,offset aDD_GEN_ONLYWRITESTREAMBOXONFIRSTSHEET
(0D9C648h)
0075D5EF jmp $LN1+9 (75EA24h)

Silly compiler. It should store the return value directly in the table and
avoid the unnecessary jump. ;)

Actually g++ does just that, unless you have a special case/default label
which for example throws an exception. Weird.

Test code:
#include <iostream>
int main()
{
std::cout << "#include <stdexcept>\n"
<< "class X { int i; };\n";
for(int i = 0; i < 100; ++i) {
std::cout << "X x" << i << ";\n";
}
std::cout << "X &blah(int x)\n"
<< "{\n"
<< "\tswitch(x) {\n";
for(int i = 0; i < 100; ++i) {
std::cout << "\tcase " << i << ":"
<< " return x" << i << ";\n";
}
// Comment in default-label for code pessimisation
std::cout << "\t//default: throw std::runtime_error(\"Oh no!\");\n"
<< "\t}\n"
<< "\treturn x0;\n"
<< "}\n";
}

resulting "jump" table:
CSWTCH.1:
.long x0
.long x1
.long x2
.long x3
.long x4
.long x5
...
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top