Statically Typed

because Hindley-Milner rocks

Template Meta-Programming and Compiler Issues


Nothing beats working with a compiler that isn’t standards compliant or has some weird bug that you just hit.  I used to work with VC++ 6.0 but was updated to 2005 about 3 years ago.  I thought after 2003 things were much better.  Such was not the case.  Let me show you the latest snafu and then explain why I went this route:

class Foo{
public:

    Foo(const char* _names) : m_Names(_names){}

    const char* getNames() const { return m_Names.c_str(); }

private:

    string m_Names;
};

typedef const char* (Foo::*Foo_Func) const;

template<Foo_Func _getFunc>
class Bar{
public:

    Bar(Foo _foo) : m_Foo(_foo){
        cout << "What the " << (m_Foo.*_getFunc)();
    }
};

typedef Bar<&Foo::getNames> ThemsFightinWords;

Now this isn’t a word for word translation (I changed the class names) of my little experiment but it should compile without a hitch. Instead I get linker errors.  Digging deeper I find that all typedef‘s of Bar have the signature Bar<0x0>.  The compiler can’t figure out the member function addresses of Foo at compile time!  Drat.

You might wonder why I would use template meta-programming instead of passing the member function pointers into Bar‘s constructor.  The answer, in this case, is not only for prettier code but also to enforce the principles of D-R-Y.  As an example look at the following.  Pay attention to the constructor:

class ClientCopy{
public:

    ClientCopy(Connection _transfer, Connection _output)
        : m_TransferDB( _transfer ),
          m_OutputDB( _output ){}

    //more stuff here
private:

    OutputDBConnection   m_OutputDB;
    TransferDBConnection m_TransferDB;
};

Looks simple, no?  To someone else there doesn’t even look like there’s magic under the hood.  If I didn’t use template meta-programming it might look like:

class ClientCopy{
public:

    ClientCopy(Connection _transfer, Connection _output)
        : m_TransferDB( _transfer, &Connection::MaxSize, &Connection::Format, &Connection::Tables ),
          m_OutputDB( _output, &Connection::ReceiveSize, &Connection::ReadFormat, &Connection::Tables ){}

private:

    DBConnection m_OutputDB;
    DBConnection m_TransferDB;
};

Which is fairly ugly and onerous to read.  Does someone maintaining my code want to have to read that?  I wouldn’t.  Moreover, will I make them memorize the relationships between Connection‘s member functions and the desired behavior of a DBConnection?

Time to play Devil’s advocate.  What if I needed instances of a DBConnection that replicated my TransferDBConnection in another class?  I’d have to repeat myself.    What if I goofed and used &Connection::Format instead of &Connection::ReadFormat?  Now I’ve got a run time bug that’ll be hell to track down!  Problems, problems, problems.

Let’s snip it in the butt with two extra lines of code:

typedef DBConnection<&Connection::MaxSize, &Connection::Format, &Connection::Tables> TransferDBConnection;
typedef DBConnection<&Connection::ReceiveSize, &Conneciton::ReadFormat, &Connection::Tables> OutputDBConnection;

and at the same time reduce the length of the constructor for DBConnection.

People like meta-programming because it makes them feel like a wizard.  Most of the time it isn’t necessary and you can get far more mileage out of something else.  Here, I like it because it enforces the principles of D-R-Y.  Mistakes will happen, isn’t it nice that you can do something to help prevent them?

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on May 17, 2010 by in C++.
%d bloggers like this: