> list

Partially Generated Classes in C++

An interesting problem which I've seen come up decently often in C++ code generators is how to deal with what I'm calling "partially generated classes". We want to generate methods and members for a class which call other methods on that class added by the implementation.

Potential Solutions

I'm not sure what the "best" solution is in this case, but I figured I'd enumerate some of the options avaliable to us with an example. We'll look at how each of these dynamically route a call to a series of impl-defined calls.

Virtual Methods

The most obvious way I've seen to implement something like this is using C++ inheritance and virtual methods, so let's start with that.

// Generated.h
class Generated
{
public:
    virtual int Case0() = 0;
    virtual int Case1() = 0;

    int RouteCall(int to);
};

// Generated.cpp
int
Generated::RouteCall(int to)
{
    switch (to) {
    case 0:
        return Case0();
    case 1:
        return Case1();
    default:
        return -1;
    }
}

// Impl.h
class Impl : public Generated
{
public:
    int Case0() override { /* ... */ }
    int Case1() override { /* ... */ }
};

The good

The bad

Curious Recurring Template Pattern

The most common solution to the virtual method approach I've seen is to use the Curious Recurring Template Pattern. This allows avoidng many of the virtual dispatch downsides, at the cost of requiring generated code end up in a header.

// Generated.h
template<typename I>
class Generated
{
public:
    // NOTE: Must be inline!
    int RouteCall(int to) {
        switch (to) {
        case 0:
            return Downcast()->Case0();
        case 1:
            return Downcast()->Case1();
        default:
            return -1;
        }
    }

private:
    I* Downcast() { return static_cast<I*>(this); }
};

// Impl.h
class Impl : public Generated<Impl>
{
public:
    int Case0() { /* ... */ }
    int Case1() { /* ... */ }
};

The good

The bad

Knowitall Base Class

I don't know of a good name for this potential solution. It's a lot like the CRTP approach, except that it takes advantage of the Codegen's ability to include the Impl's definition in its cpp file to avoid the template parameter.

I call it a Knowitall Class because it claims to know exactly who is subclassing it, and just downcasts the class hierarchy away.

// Codegen.h
class Impl;

class Generated
{
public:
    int RouteCall(int to);

private:
    Impl* Downcast();
};

// Codegen.cpp
#include "Impl.h"

Impl*
Generated::Downcast()
{
    return static_cast<Impl*>(this);
}

int
Generated::RouteCall(int to)
{
    switch (to) {
    case 0:
        return Downcast()->Case0();
    case 1:
        return Downcast()->Case1();
    default:
        return -1;
    }
}

// Impl.h
class Impl : public Generated
{
public:
    int Case0() { /* ... */ }
    int Case1() { /* ... */ }
};

The good

The bad

Member Declaration Macros

This approach uses a macro to inject the needed method declarations directly into the impl class, which avoids the need for the Generated base class which should only have one subclass.

// Generated.h
#define DECL_GENERATED_FOR_IMPL() \
    public:                       \
        int RouteCall(int to);    \
    /* ... */                     \
    public:

// Generated.cpp
#include "Impl.h"

int
Impl::RouteCall(int to)
{
    switch (to) {
    case 0:
        return Case0();
    case 1:
        return Case1();
    default:
        return -1;
    }
}

// Impl.h
class Impl
{
    DECL_GENERATED_FOR_IMPL()

public:
    int Case0() { /* ... */ }
    int Case1() { /* ... */ }
};

The good

The bad

Freestanding Functions

Finally, we can take the function-call approach and not declare any methods on Impl at all, instead declaring freestanding methods and using function overloading.

// Generated.h
int RouteCall(Impl* self, int to);

// Generated.cpp
int
RouteCall(Impl* self, int to)
{
    switch (to) {
    case 0:
        return self->Case0();
    case 1:
        return self->Case1();
    default:
        return -1;
    }
}

// Impl.h
class Impl
{
public:
    int Case0() { /* ... */ }
    int Case1() { /* ... */ }
};

The good

The bad