Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

case/switch metaprogramming

9 views
Skip to first unread message

Aaron Graham

unread,
Jul 24, 2007, 3:26:34 PM7/24/07
to
I have a switch/case statement that looks something like this:

std::auto_ptr<Dev> dev;
switch (dev_id) {
case 1: dev = std::auto_ptr<Dev>(new devices::Device<1>()); break;
case 2: dev = std::auto_ptr<Dev>(new devices::Device<2>()); break;
case 3: dev = std::auto_ptr<Dev>(new devices::Device<3>()); break;
case 4: dev = std::auto_ptr<Dev>(new devices::Device<4>()); break;
case 5: dev = std::auto_ptr<Dev>(new devices::Device<5>()); break;
....
case 47: dev = std::auto_ptr<Dev>(new devices::Device<47>()); break;
default: dev = std::auto_ptr<Dev>(new devices::Device<0>()); break;
}

Note that the switch can grow arbitrarily large and is very
repetitive. Is there an easy way to use metaprogramming to get the
compiler to generate all this code?


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Victor Bazarov

unread,
Jul 24, 2007, 6:56:33 PM7/24/07
to
Aaron Graham wrote:
> I have a switch/case statement that looks something like this:
>
> std::auto_ptr<Dev> dev;
> switch (dev_id) {
> case 1: dev = std::auto_ptr<Dev>(new devices::Device<1>()); break;
> case 2: dev = std::auto_ptr<Dev>(new devices::Device<2>()); break;
> case 3: dev = std::auto_ptr<Dev>(new devices::Device<3>()); break;
> case 4: dev = std::auto_ptr<Dev>(new devices::Device<4>()); break;
> case 5: dev = std::auto_ptr<Dev>(new devices::Device<5>()); break;
> ....
> case 47: dev = std::auto_ptr<Dev>(new devices::Device<47>()); break;
> default: dev = std::auto_ptr<Dev>(new devices::Device<0>()); break;
> }
>
> Note that the switch can grow arbitrarily large and is very
> repetitive. Is there an easy way to use metaprogramming to get the
> compiler to generate all this code?

Yes, I believe so. Check this out:

// --------------------- this is just to make it compile
#include <iostream>

class Dev {
public:
virtual ~Dev() {}
};

template<unsigned id> class Device : public Dev {
static const unsigned s_id = id;
public:
Device() { std::cout << "Creating Device<" << id << ">\n"; }
};

// --------------------- here begins the important part:
// presuming creating all devices between 1 and 'hi_id'
template<unsigned hi_id>
Dev* getDeviceFrom(unsigned dev_id) {
if (dev_id > hi_id || dev_id == 0) // here is your 'default:'
return new Device<0>;
if (dev_id == hi_id)
return new Device<hi_id>; // Bingo!
else // dev_id < hi_id
return getDeviceFrom<hi_id-1>(dev_id);
}

template<>
Dev* getDeviceFrom<1>(unsigned i) {
return new Device<1>();
}
// -------------------------------------------------------

#include <time.h>
int main()
{
srand((unsigned)time(0));
Dev *p[10];

for (int i = 0; i < 10; ++i) {

// here instead of the 'switch' I have
unsigned dev_id = rand() % 20; // or whatever
p[i] = getDeviceFrom<30>(dev_id); // out of 30

}

for (int i = 0; i < 10; ++i)
delete p[i];
}

The code is recursive, and you will possibly run quite soon out of
compiler resources (just like with other recursive templates), so
please adjust the total number of devices (here I have 30) to your
bare minimum.

Enjoy!

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask

Aaron Graham

unread,
Jul 25, 2007, 2:38:37 AM7/25/07
to

Very nice. One improvement on that: I can get the same effect without
the special "default" case in the generalized version of the function:

template<unsigned hi_id>
Dev* getDeviceFrom(unsigned dev_id) {

if (dev_id == hi_id)
return new Device<hi_id>; // Bingo!

return getDeviceFrom<hi_id-1>(dev_id);
}

template<>
Dev* getDeviceFrom<0>(unsigned i) {
return new Device<0>(); // default case here
}

That should generate less code.

Now I'm trying to figure out a way to get it to "skip over" devices
that aren't defined, to fall back on the default case. For instance,
if Device<12> isn't defined, I want a Device<0>, but this
implementation will give me a compile time error instead.
Essentially, I need the compiler to elide the "if" clause for
getDeviceFrom<12> when it finds that type is not declared.

Aaron

{ signature and clc++m banner removed -mod }

--

Victor Bazarov

unread,
Jul 25, 2007, 8:45:16 PM7/25/07
to


This is significantly different semantically from your 'switch'
statement. If 'dev_id' is _greater_ than the highest number, the
'switch' statement will go into 'default', but in this case it
will create the "Device" with the highest 'id'. I don't think
that that's what you want.

> }
>
> template<>
> Dev* getDeviceFrom<0>(unsigned i) {
> return new Device<0>(); // default case here
> }
>
> That should generate less code.

With different semantics.

> Now I'm trying to figure out a way to get it to "skip over" devices
> that aren't defined, to fall back on the default case. For instance,
> if Device<12> isn't defined, I want a Device<0>, but this
> implementation will give me a compile time error instead.
> Essentially, I need the compiler to elide the "if" clause for
> getDeviceFrom<12> when it finds that type is not declared.

That's relatively simple. Here is another indirection -- to
calculate the type to 'new' depending on whether to skip it or
not:

template<unsigned id_> struct skip_id { enum { yes = 0 }; };

template<unsigned id> struct which_Device {
typedef Device< skip_id<id>::yes ? 0 : id > Device;
};

template<unsigned hi_id>
Dev* getDeviceFrom(unsigned dev_id) {
if (dev_id > hi_id || dev_id == 0) // here is your 'default:'
return new Device<0>;
if (dev_id == hi_id)

return new which_Device<hi_id>::Device; // Bingo!
else // dev_id < hi_id
return getDeviceFrom<hi_id - 1>(dev_id);
}

template<>
Dev* getDeviceFrom<1>(unsigned i) {

if (skip_id<1>::yes)


return new Device<0>();

else


return new Device<1>();
}

// how, to skip 12, 43, and 12345:
template<> struct skip_id<12> { enum { yes = 1 }; };
template<> struct skip_id<27> { enum { yes = 1 }; };
template<> struct skip_id<12345> { enum { yes = 1 }; };


V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Dennis Jones

unread,
Jul 25, 2007, 8:47:14 PM7/25/07
to

"Aaron Graham" <atgr...@gmail.com> wrote in message
news:1185319852.8...@z24g2000prh.googlegroups.com...

> Very nice. One improvement on that: I can get the same effect without
> the special "default" case in the generalized version of the function:
>
> template<unsigned hi_id>
> Dev* getDeviceFrom(unsigned dev_id) {
> if (dev_id == hi_id)
> return new Device<hi_id>; // Bingo!
> return getDeviceFrom<hi_id-1>(dev_id);
> }
>
> template<>
> Dev* getDeviceFrom<0>(unsigned i) {
> return new Device<0>(); // default case here
> }
>
> That should generate less code.
>
> Now I'm trying to figure out a way to get it to "skip over" devices
> that aren't defined, to fall back on the default case. For instance,
> if Device<12> isn't defined, I want a Device<0>, but this
> implementation will give me a compile time error instead.
> Essentially, I need the compiler to elide the "if" clause for
> getDeviceFrom<12> when it finds that type is not declared.

It seems to me there are at least two (quick and dirty) ways to do that, but
both of them require something extra (this is untested):

1) Add an if clause for the special case:

template<unsigned hi_id>
Dev* getDeviceFrom(unsigned dev_id) {
if (dev_id == hi_id)
return new Device<hi_id>; // Bingo!

if (dev_id == 12)
return new Device<0>; // Device<12> not defined, use default
return getDeviceFrom<hi_id-1>(dev_id);
}

or 2) add a new template function for the special case:

template<>
Dev* getDeviceFrom<12>(unsigned i) {
return new Device<0>(); // Device<12> not defined, use default
}


There are probably much more elegant ways, but these were the most obvious.

- Dennis

Aaron Graham

unread,
Jul 25, 2007, 8:47:06 PM7/25/07
to
OK, I figured it out. Using Victor's code as a starting point, I
added the ability to add/remove individual cases automatically:

#include <iostream>
using namespace std;

struct Dev {
static bool const is_supported = true;
virtual ~Dev() {}
};

template <int id> struct Device {
static bool const is_supported = false;
};

template <> struct Device<0> : public Dev {
virtual ~Device() { cout << 0 << endl; }
};
template <> struct Device<99> : public Dev {
virtual ~Device() { cout << 99 << endl; }
};

// ----- now for the switch/case statement -----
template <bool is_supported, int n> struct device_selector;

template <int n>
struct device_selector<true, n> { // supported devices
Dev* operator()(int const id) {
if (n == id) return new Device<n>();
device_selector<Device<n-1>::is_supported, n-1> s;
return s(id);
}
};

template <int n>
struct device_selector<false, n> { // unsupported devices
Dev* operator()(int const id) {
device_selector<Device<n-1>::is_supported, n-1> s;
return s(id);
}
};

template <>
struct device_selector<true, 0> { // default case is supported
Dev* operator()(int const id) {


return new Device<0>();
}

};

Dev* get_device(int const id) {
int const high = 240;
device_selector<Device<high>::is_supported, high> s;
return s(id);
}
// ----- end of switch/case statement ---

#include <iostream>
int main() {
delete get_device(99); // prints "99"
delete get_device(12); // prints "0"
}

Now the only thing I need to do in order to add something to the
switch statement is implement it! (And make sure the declaraion is
#included, of course.)

The default template depth of the gcc I'm currently using is 500, and
that can be increased with a command-line switch. I would expect any
good compiler to support several hundred by default at least, and to
provide a mechanism for supporting many more.

Additionally, it looks like gcc optimizes this even better than the
explicit switch/case at -O3. In fact, the generated code is
completely flat! It's all inlined, so there is essentially no extra
runtime cost.

Very nice. Now if I could use function partial specialization, this
code would be even more concise.

Aaron


--

werasm

unread,
Jul 26, 2007, 11:11:25 AM7/26/07
to

Aaron Graham wrote:
> I have a switch/case statement that looks something like this:
>
[snip]

Hi Aaron. Excuse my previous code that did not compile. Here is my
final solution. I knew I did something
similar before, but I forgot to terminate the index, as well as some
other errors. This compiles under GCC
and I suppose others. You can skip the first part. DeviceAt is the
important part.

#include <memory>
#include <stdexcept>
#include <boost/static_assert.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/comparison.hpp>

namespace devices{

struct Dev{ virtual ~Dev(){} };
template <int N>
struct Device : Dev
{
};
}//end devices

template <int NMax, int N = 0>
struct DeviceAt
{
template <int M> struct Index
{
typedef boost::mpl::int_<M> CurIndex;
typedef boost::mpl::int_<NMax-1> MaxIndex;
struct type : boost::mpl::if_<
boost::mpl::greater<CurIndex, MaxIndex>, MaxIndex,
CurIndex>::type
{ };
};

template <int M>
static std::auto_ptr<devices::Dev> doGet()
{
using namespace devices;
BOOST_STATIC_ASSERT( M < NMax );
return std::auto_ptr<Dev>( new Device<M>() );
}
static std::auto_ptr<devices::Dev> get( unsigned idx )
{
switch( idx )
{
case N+0: return doGet<N+0>();
default: return
DeviceAt<NMax, Index<N+1>::type::value>::get( idx );
}
}
};

int main()
{
using namespace devices;
std::auto_ptr<Dev> dv( DeviceAt<300>::get( 4 ) );
return 0;
};

werasm

unread,
Jul 26, 2007, 11:10:30 AM7/26/07
to

Aaron Graham wrote:
> I have a switch/case statement that looks something like this:
>
> std::auto_ptr<Dev> dev;
> switch (dev_id) {
> case 1: dev = std::auto_ptr<Dev>(new devices::Device<1>()); break;
> case 2: dev = std::auto_ptr<Dev>(new devices::Device<2>()); break;
> case 3: dev = std::auto_ptr<Dev>(new devices::Device<3>()); break;
> case 4: dev = std::auto_ptr<Dev>(new devices::Device<4>()); break;
> case 5: dev = std::auto_ptr<Dev>(new devices::Device<5>()); break;
> ....
> case 47: dev = std::auto_ptr<Dev>(new devices::Device<47>()); break;
> default: dev = std::auto_ptr<Dev>(new devices::Device<0>()); break;
> }

template <class N>
class DeviceAt
{
static std::auto_ptr<Dev> get( unsigned idx )
{
switch( idx )
{
case N+0: return std::auto_ptr<Dev>( new devices::Device<N
+0>() );
case N+1: return std::auto_ptr<Dev>( new devices::Device<N
+1>() );
case N+2: return std::auto_ptr<Dev>( new devices::Device<N
+2>() );
default: return DeviceAt<N+3>::get( idx );
}
}
}

With a little trouble you can delay the creation of std::auto_ptr and
check the index like so:

//Complete code....

#include <memory>
#include <stdexcept>

namespace devices{

struct Dev{ virtual ~Dev(){} };
template <int N>
struct Device : Dev
{
};
}


//-----------------Important part---------------------
template <int N, int NMax>
class DeviceAt
{
struct no_such_device : std::exception
{
virtual const char* what() const throw()
{
return "no such device";


}
};
template <int M>
static std::auto_ptr<devices::Dev> doGet()
{
using namespace devices;

if( M > NMax ){ throw no_such_device(); }
else
{


return std::auto_ptr<Dev>(
new Device<M>() );
}
}
static std::auto_ptr<devices::Dev>
get( unsigned idx )
{
switch( idx )
{
case N+0: return doGet<N+0>();

case N+1: return doGet<N+1>();
case N+2: return doGet<N+2>();
case N+3: return doGet<N+0>();
case N+4: return doGet<N+1>();
default:
return typename::DeviceAt<N+5,NMax>::get( idx );
}
}
};

Regards,

Werner

werasm

unread,
Jul 27, 2007, 10:56:14 AM7/27/07
to

werasm wrote:

> Aaron Graham wrote:
> Hi Aaron. Excuse my previous code that did not compile. Here is my
> final solution.

[snip]

Hi again,

The problem with my previous final was that it was too linear.
Perhaps
compilers solve those things though, but I found that this take works
better. Any critique welcome. T'was a nice challenge (I suppose for
others,
a lesser one:-).

I would have liked to assert statically if the runtime index was out
of range. Alas.

DeviceAt now looks like this:

template <int NMax, int N = 0>
struct DeviceAt
{

public:


static std::auto_ptr<devices::Dev>

get( int idx )
{
checkIdxRange( idx );

enum{ mid = (NMax+N)/2 };
if( idx < mid )
{
return DeviceAt<mid, N>::get( idx );
}
else if( idx > mid )
{
return DeviceAt<NMax, mid>::get( idx );
}
else //Idx equals mid - Bingo?
{
return doGet<mid>();
}
}

private:
static void checkIdxRange( int idx )
{
if( idx > NMax )
{
throw std::out_of_range(
"DeviceAt::get(): Idx out of range!" );


}
}
template <int M>
static std::auto_ptr<devices::Dev> doGet()
{
using namespace devices;

return std::auto_ptr<Dev>( new Device<M>() );
}

};

Regards,

Werner

Falk Tannhäuser

unread,
Jul 28, 2007, 8:12:07 PM7/28/07
to
Aaron Graham schrieb:

>> Aaron Graham wrote:
>>> I have a switch/case statement that looks something like this:
>>> std::auto_ptr<Dev> dev;
>>> switch (dev_id) {
>>> case 1: dev = std::auto_ptr<Dev>(new devices::Device<1>()); break;
>>> case 2: dev = std::auto_ptr<Dev>(new devices::Device<2>()); break;
>>> case 3: dev = std::auto_ptr<Dev>(new devices::Device<3>()); break;
>>> case 4: dev = std::auto_ptr<Dev>(new devices::Device<4>()); break;
>>> case 5: dev = std::auto_ptr<Dev>(new devices::Device<5>()); break;
>>> ....
>>> case 47: dev = std::auto_ptr<Dev>(new devices::Device<47>()); break;
>>> default: dev = std::auto_ptr<Dev>(new devices::Device<0>()); break;
>>> }
>>> Note that the switch can grow arbitrarily large and is very
>>> repetitive. Is there an easy way to use metaprogramming to get the
>>> compiler to generate all this code?

How about using lookup in an array of function pointers, populated
without too much typing through some preprocessor magic:

#include <memory>

struct Dev
{
virtual ~Dev() {}
};

template<int N> struct Device : public Dev
{
};

template<int N> std::auto_ptr<Dev> do_createDev()
{
return std::auto_ptr<Dev>(new Device<N>);
}

typedef std::auto_ptr<Dev> create_func_t();

#define DO_CREATE_DEV(n) do_createDev<n>
#define P1(n, f) f(10*(n) ), f(10*(n)+1), f(10*(n)+2), f(10*(n)+3),
f(10*(n)+4), \
f(10*(n)+5), f(10*(n)+6), f(10*(n)+7), f(10*(n)+8),
f(10*(n)+9)
#define P2(n, f) P1(10*(n) , f), P1(10*(n)+1, f), P1(10*(n)+2, f),
P1(10*(n)+3, f), P1(10*(n)+4, f), \
P1(10*(n)+5, f), P1(10*(n)+6, f), P1(10*(n)+7, f),
P1(10*(n)+8, f), P1(10*(n)+9, f)
#define P3(f) P2(0, f), P2(1, f), P2(2, f), P2(3, f), P2(4, f), \
P2(5, f), P2(6, f), P2(7, f), P2(8, f), P2(9, f)

std::auto_ptr<Dev> createDev(int dev_id)
{
static create_func_t* const func_array[] =
{
P3(DO_CREATE_DEV)
};
if(dev_id >= 0 && dev_id < sizeof func_array / sizeof func_array[0])
return func_array[dev_id]();
else
return func_array[0]();
}

In this example, an array of 1000 function pointers is initialised with
pointers from do_createDev<0> to do_createDev<999>.

Falk

0 new messages