display | more...

I don't do much C++ programming for practical purposes anymore; I prefer languages who like the programmer, like Perl. But I maintain that C++ remains the best language for intellectual challenges. And this is all thanks to templates.

Here's one I was asked to do by a friend: make a type that will behave polymorphically as n types, depending on how you use it. C++ lacks the contextual information to do this as well as, again, Perl, but it's possible to some extent, and the solution is interesting.

It makes use of typelists, which are put together as such (I'll employ my own capitalization scheme for utility classes):

    struct null;    // Never defined :-)
    
    template <class Head, class Tail>
    struct typelist { };

    #define TL_1(a)       typelist<a, null>
    #define TL_2(a,b)     typelist< a,TL_1(b) >
    #define TL_3(a,b,c)   typelist< a,TL_2(b,c) >
    // ...  define as many as you need

Now we'll need a delegate class: one that just holds some data of another type, and forwards to it whenever possible. The closest C++ comes to this is with the typecast operator. If you define

    operator SomeType& () { ... }

Then there are a lot of cases where you can use the type in which that is defined just like SomeType. (Unfortunately, there are also a lot of cases where you can't. But this is the best we can do.)

So this is going to work by building an inheritance chain of each type we need to build the final type. Template specialization will let us extract each type out of the list.

    template <class List>
    class multitype;

    template <class Head, class Tail>
    class multitype< typelist<Head, Tail> >
        : public multitype<Tail>
    {
    public:
        operator Head& () { return data_; }
    private:
        Head data_;
    };

    template <>
    class multitype<null>
    { };

That's it! Here's a little test program:

    #include <iostream>
    #include <string>
    using namespace std;   // Don't normally do this, but...

    void iprint(int data) { cout << data << endl; }
    void fprint(float data) { cout << data << endl; }
    void sprint(string data) { cout << data << endl; }

    int main() {
        multitype< TL_3(int, float, string) > mt;
        (int&)   mt = 42;
        (float&) mt = 3.1415;
        (string&)mt = "Hello, Multitype!";

        iprint(mt);  // Prints 42
        fprint(mt);  // Prints 3.1415
        sprint(mt);  // Prints Hello, Multitype!
    }

See how that works? mt is built from the following heirarchy:

            multitype<null>
                   ^
                   |
         multitype< TL_1(string) >
                   ^
                   |
       multitype< TL_2(float, string) >
                   ^
                   |
      multitype< TL_3(int, float, string) >

Each time, the front type is where the corresponding typecast is defined.

Wherever it's ambiguous you can typecast to get the correct behavior. Anyway, have fun abusing templates.

Log in or register to write something here or to contact authors.