Re: case/switch metaprogramming
 
Aaron Graham wrote:
On Jul 24, 3:56 pm, "Victor Bazarov" <v.Abaza...@comAcast.net> wrote:
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!
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);
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 ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]