Question

I have a class called caRender, which provides one caRender::renderClientObject() method per given object type in clientObjectTypes. So the following code snipped shows this running situation:

#define UNUSED(x) (void)(x)

typedef boost::mpl::vector<Model::ClientModel::cClientVerticesObject,
        Model::ClientModel::cRawClientObject> clientObjectTypes;

template <class T>
    struct methodForward{
        virtual void renderClientObject(T* clientObject,
            Controller::QtOpenGL::cQOpenGLContext* glContext) {
                UNUSED(clientObject);
                UNUSED(glContext);
                };
    };

struct base {
    template<class baseClass, class T>
    struct apply {

        struct deriveRender: baseClass, methodForward<T> {
            virtual ~deriveRender(){};

            using baseClass::renderClientObject;
            using methodForward<T>::renderClientObject;
        };

        typedef deriveRender type;
    };

    template<class T>
    struct apply<void, T> {

        struct startRender : methodForward<T> {
            virtual ~startRender(){};
        };

        typedef startRender type;
    };

};

typedef boost::mpl::fold<clientObjectTypes, void, base>::type caRender;

Q: I want the parameter abstraction also for my second parameter (context) inside the renderClientObject method. So the goal is to get n*m generated renderClientObject methods, where n is defined as the number of clientObjectTypes and m is the number of context types. I would add a second vector:

typedef boost::mpl::vector<Controller::QtOpenGL::cQOpenGLContext> contextTypes;

But than i have to idea how to proceed, because i am very new to the topic of meta programming.

Was it helpful?

Solution

Update I've since provided an updated version that does match your question more closely (by dispatching on client object and and context). See Live On Coliru.

Update 2 Posted a version adding type-erasure to the mix to get a virtual interface while retaining the other benefits. See Live On Coliru Posting it at the bottom to ensure future retention on SO.

I'm converting my comment into an answer because it provides more space to elaborate.

I get the impression you're "just" trying to get multi-method like semantics (i.e. functions which have polymorphic behavior that depends on more than one object type).

Enter the actors

For this demonstration I'll use some stub types. Let's assume 3 client object types[1]:

namespace Model { namespace ClientModel { 
     struct cClientVerticesObject : boost::noncopyable {};
     struct cRawClientObject      : boost::noncopyable {};
     struct cFunkyClientObject    : boost::noncopyable {};
} }

Model::ClientModel::cClientVerticesObject vertices;
Model::ClientModel::cRawClientObject      raw;
Model::ClientModel::cFunkyClientObject    funky;

Let's see how the story of these objects unfolds :)


Static Dispatch; Starting with the basics

In that case I suspect that a run-of-the-mill polymorphic functor could be much more to the point:

struct RenderClientObjects
{
    typedef void result_type;

    RenderClientObjects(Controller::QtOpenGL::cQOpenGLContext* glContext) 
        : glContext(glContext) 
    { }

    template <typename ClientObject1, typename ClientObject2> 
        void operator()(ClientObject1 const& clientObject1, ClientObject2 const& clientObject2) const
        {
             // some implementation
        }

private: 
    Controller::QtOpenGL::cQOpenGLContext* glContext;
};

You can use this like any calleable object, relying on static dispatch:

RenderClientObjects multimethod(&glContext);

multimethod(vertices, vertices);
multimethod(vertices, funky);
multimethod(raw, vertices);
multimethod(funky, vertices);

Runtime Dispatch: enter boost::variant!

Skipping the question of how // some implementation will be provided, let me jump ahead to where the elegance of this shines: you can magically have runtime binary dispatch using boost::variant:

/////////////////////////////////////////////////////////////////////
// Variant dispatch (boost apply_visitor supports binary dispatch)
typedef boost::variant<
        Model::ClientModel::cClientVerticesObject&, 
        Model::ClientModel::cRawClientObject&,
        Model::ClientModel::cFunkyClientObject&
    > ClientObjectVariant;

void variant_multimethod(Controller::QtOpenGL::cQOpenGLContext& ctx, ClientObjectVariant const& a, ClientObjectVariant const& b)
{
    boost::apply_visitor(RenderClientObjects(&ctx), a, b);
}

Providing (custom) implementations

Of course, you will still want to provide implementations for those overloads. You can

  • supply explicit overloads
  • delegate to an implementation class that can be specialized (sometimes known as customization point, extension point or userdefined hook etc.)
  • combinations of that

Full Sample

Here's a full sample using all the approaches mentioned and showing exhaustive static and runtime dispatch.

See it Live On Coliru

#include <boost/variant.hpp>
#include <boost/utility.hpp>
#include <iostream>

////// STUBS
namespace Model { namespace ClientModel { 
     struct cClientVerticesObject : boost::noncopyable {};
     struct cRawClientObject      : boost::noncopyable {};
     struct cFunkyClientObject    : boost::noncopyable {};
} }
namespace Controller { namespace QtOpenGL { 
    typedef std::ostream cQOpenGLContext;
} }
////// END STUBS

/////////////////////////////////////////////////////////////////////
// Why not **just** make it a polymorphic functor?
//
// You can make it use an extension point if you're so inclined:
// (note the use of Enable to make it SFINAE-friendly)
namespace UserTypeHooks
{
    template <typename ClientObject1, typename ClientObject2, typename Enable = void> 
    struct RenderClientObjectsImpl
    {
        void static call(
                Controller::QtOpenGL::cQOpenGLContext* glContext, 
                ClientObject1 const& clientObject1,
                ClientObject2 const& clientObject2)
        {
            (*glContext) << __PRETTY_FUNCTION__ << "\n";
        }
    };
}

struct RenderClientObjects
{
    typedef void result_type;

    RenderClientObjects(Controller::QtOpenGL::cQOpenGLContext* glContext) 
        : glContext(glContext) 
    { }

    //
    void operator()(Model::ClientModel::cFunkyClientObject const& clientObject1, Model::ClientModel::cFunkyClientObject const& clientObject2) const
    {
        (*glContext) << "Both objects are Funky.\n";
    }

    template <typename ClientObject2> 
        void operator()(Model::ClientModel::cFunkyClientObject const& clientObject1, ClientObject2 const& clientObject2) const
        {
            (*glContext) << "Funky object involved (other is " << typeid(clientObject2).name() << ")\n";
        }

    template <typename ClientObject1> 
        void operator()(ClientObject1 const& clientObject1, Model::ClientModel::cFunkyClientObject const& clientObject2) const
        {
            (*this)(clientObject2, clientObject1); // delegate implementation, for example
        }

    // catch all:
    template <typename ClientObject1, typename ClientObject2> 
        void operator()(ClientObject1 const& clientObject1, ClientObject2 const& clientObject2) const
        {
            return UserTypeHooks::RenderClientObjectsImpl<ClientObject1, ClientObject2>::call(glContext, clientObject1, clientObject2);
        }

  private: 
    Controller::QtOpenGL::cQOpenGLContext* glContext;
};

/////////////////////////////////////////////////////////////////////
// Demonstrating the user-defined extension point mechanics:
namespace UserTypeHooks
{
    template <typename ClientObject>
    struct RenderClientObjectsImpl<ClientObject, ClientObject>
    {
        void static call(
                Controller::QtOpenGL::cQOpenGLContext* glContext, 
                ClientObject const& clientObject1,
                ClientObject const& clientObject2)
        {
            (*glContext) << "Both objects are of the same type (and not funky) : " << typeid(ClientObject).name() << "\n";
        }
    };
}

/////////////////////////////////////////////////////////////////////
// Variant dispatch (boost apply_visitor supports binary dispatch)
typedef boost::variant<
        Model::ClientModel::cClientVerticesObject&, 
        Model::ClientModel::cRawClientObject&,
        Model::ClientModel::cFunkyClientObject&
    > ClientObjectVariant;

void variant_multimethod(Controller::QtOpenGL::cQOpenGLContext& ctx, ClientObjectVariant const& a, ClientObjectVariant const& b)
{
    RenderClientObjects multimethod(&ctx);
    boost::apply_visitor(multimethod, a, b);
}

int main()
{
    Controller::QtOpenGL::cQOpenGLContext glContext(std::cout.rdbuf());
    RenderClientObjects multimethod(&glContext);

    Model::ClientModel::cClientVerticesObject vertices;
    Model::ClientModel::cRawClientObject      raw;
    Model::ClientModel::cFunkyClientObject    funky;

    glContext << "// Fully static dispatch:\n";
    glContext << "//\n";
    multimethod(vertices, vertices);
    multimethod(vertices, raw);
    multimethod(vertices, funky);
    //
    multimethod(raw, vertices);
    multimethod(raw, raw);
    multimethod(raw, funky);
    //
    multimethod(funky, vertices);
    multimethod(funky, raw);
    multimethod(funky, funky);

    glContext << "\n";
    glContext << "// Runtime dispatch:\n";
    glContext << "//\n";

    variant_multimethod(glContext, vertices, vertices);
    variant_multimethod(glContext, vertices, raw);
    variant_multimethod(glContext, vertices, funky);
    //
    variant_multimethod(glContext, raw, vertices);
    variant_multimethod(glContext, raw, raw);
    variant_multimethod(glContext, raw, funky);
    //
    variant_multimethod(glContext, funky, vertices);
    variant_multimethod(glContext, funky, raw);
    variant_multimethod(glContext, funky, funky);
}

Oh, for completeness, here's the output:

g++-4.8 -Os -Wall -pedantic main.cpp && ./a.out | c++filt -t
// Fully static dispatch:
//
Both objects are of the same type (and not funky) : Model::ClientModel::cClientVerticesObject
static void UserTypeHooks::RenderClientObjectsImpl<ClientObject1, ClientObject2, Enable>::call(Controller::QtOpenGL::cQOpenGLContext*, const ClientObject1&, const ClientObject2&) [with ClientObject1 = Model::ClientModel::cClientVerticesObject; ClientObject2 = Model::ClientModel::cRawClientObject; Enable = void; Controller::QtOpenGL::cQOpenGLContext = std::basic_ostream<char>]
Funky object involved (other is Model::ClientModel::cClientVerticesObject)
static void UserTypeHooks::RenderClientObjectsImpl<ClientObject1, ClientObject2, Enable>::call(Controller::QtOpenGL::cQOpenGLContext*, const ClientObject1&, const ClientObject2&) [with ClientObject1 = Model::ClientModel::cRawClientObject; ClientObject2 = Model::ClientModel::cClientVerticesObject; Enable = void; Controller::QtOpenGL::cQOpenGLContext = std::basic_ostream<char>]
Both objects are of the same type (and not funky) : Model::ClientModel::cRawClientObject
Funky object involved (other is Model::ClientModel::cRawClientObject)
Funky object involved (other is Model::ClientModel::cClientVerticesObject)
Funky object involved (other is Model::ClientModel::cRawClientObject)
Both objects are Funky.

// Runtime dispatch:
//
Both objects are of the same type (and not funky) : Model::ClientModel::cClientVerticesObject
static void UserTypeHooks::RenderClientObjectsImpl<ClientObject1, ClientObject2, Enable>::call(Controller::QtOpenGL::cQOpenGLContext*, const ClientObject1&, const ClientObject2&) [with ClientObject1 = Model::ClientModel::cClientVerticesObject; ClientObject2 = Model::ClientModel::cRawClientObject; Enable = void; Controller::QtOpenGL::cQOpenGLContext = std::basic_ostream<char>]
Funky object involved (other is Model::ClientModel::cClientVerticesObject)
static void UserTypeHooks::RenderClientObjectsImpl<ClientObject1, ClientObject2, Enable>::call(Controller::QtOpenGL::cQOpenGLContext*, const ClientObject1&, const ClientObject2&) [with ClientObject1 = Model::ClientModel::cRawClientObject; ClientObject2 = Model::ClientModel::cClientVerticesObject; Enable = void; Controller::QtOpenGL::cQOpenGLContext = std::basic_ostream<char>]
Both objects are of the same type (and not funky) : Model::ClientModel::cRawClientObject
Funky object involved (other is Model::ClientModel::cRawClientObject)
Funky object involved (other is Model::ClientModel::cClientVerticesObject)
Funky object involved (other is Model::ClientModel::cRawClientObject)
Both objects are Funky.

The Type-Erased Interface Version

Full codefor 'Update 2':

#include <typeinfo>
#include <boost/type_traits.hpp>
#include <iostream>

////// STUBS
struct move_only {  // apparently boost::noncopyable prohibits move too
    move_only(move_only const&) = delete;
    move_only(move_only&&) = default;
    move_only() = default;
};

namespace Model { namespace ClientModel { 
     struct cClientVerticesObject : move_only {};
     struct cRawClientObject      : move_only {};
     struct cFunkyClientObject    : move_only {};
} }
namespace Controller { 
    namespace QtOpenGL { 
        struct cQOpenGLContext : move_only {};
    } 
    struct cConsoleContext : move_only {};
    struct cDevNullContext : move_only {};
}

namespace traits
{
    template <typename T> struct supports_console_ctx : boost::mpl::false_ {};
    template <> 
        struct supports_console_ctx<Model::ClientModel::cFunkyClientObject> : boost::mpl::true_ {};
}
////// END STUBS

/////////////////////////////////////////////////////////////////////
// Why not **just** make it a polymorphic functor?
//
// You can make it use an extension point if you're so inclined:
// (note the use of Enable to make it Sfinae-friendly)
namespace UserTypeHooks
{
    template <typename ClientObject, typename Context, typename Enable = void> 
    struct RenderClientObjectsImpl
    {
        void static call(ClientObject const& clientObject, Context const& context)
        {
            // static_assert(false, "not implemented");
            // throw?
            std::cout << "NOT IMPLEMENTED:\t" << __PRETTY_FUNCTION__ << "\n";
        }
    };

    template <typename ClientObject> 
        struct RenderClientObjectsImpl<ClientObject, Controller::QtOpenGL::cQOpenGLContext>
    {
        void static call(ClientObject const& clientObject, Controller::QtOpenGL::cQOpenGLContext const& context)
        {
            std::cout << "cQOpenGLContext:\t" << typeid(ClientObject).name() << "\n";
        }
    };

    template <typename ClientObject> 
        struct RenderClientObjectsImpl<ClientObject, Controller::cDevNullContext>
    {
        void static call(ClientObject const& clientObject, Controller::cDevNullContext const& context)
        {
            std::cout << "devnull:\t\t" << typeid(ClientObject).name() << "\n";
        }
    };
}

struct RenderClientObjects
{
    typedef void result_type;

    template <typename ClientObject, typename Context> 
        void operator()(ClientObject const& clientObject, Context const& context) const
        {
            return UserTypeHooks::RenderClientObjectsImpl<ClientObject, Context>::call(clientObject, context);
        }
};

/////////////////////////////////////////////////////////////////////
// Demonstrating the user-defined extension point mechanics:
namespace UserTypeHooks
{
    template <typename ClientObject>
    struct RenderClientObjectsImpl<ClientObject, Controller::cConsoleContext,
        typename boost::enable_if<traits::supports_console_ctx<ClientObject> >::type>
    {
        void static call(
                ClientObject const& clientObject,
                Controller::cConsoleContext const& context)
        {
            std::cout << "This type has cConsoleContext support due to the supports_console_ctx trait! " << typeid(ClientObject).name() << "\n";
        }
    };
}

/////////////////////////////////////////////////////////////////////
// Added: Dynamic interface
//
// Making this a bit more complex than you probably need, but hey, assuming the
// worst:
#include <memory>

struct IPolymorphicRenderable
{
    // you likely require only one of these, and it might not need to be
    // virtual
    virtual void render(Controller::QtOpenGL::cQOpenGLContext& ctx) = 0;
    virtual void render(Controller::cConsoleContext& ctx) = 0;
    virtual void render(Controller::cDevNullContext& ctx) = 0;
};

struct IClientObject : IPolymorphicRenderable
{
    template <typename T> IClientObject(T&& val) : _erased(new erasure<T>(std::forward<T>(val))) { }

    virtual void render(Controller::QtOpenGL::cQOpenGLContext& ctx) { return _erased->render(ctx); }
    virtual void render(Controller::cConsoleContext& ctx)           { return _erased->render(ctx); }
    virtual void render(Controller::cDevNullContext& ctx)           { return _erased->render(ctx); }

  private:
    template <typename T> struct erasure : IPolymorphicRenderable
    {
        erasure(T val) : _val(std::move(val)) { }

        void render(Controller::QtOpenGL::cQOpenGLContext& ctx) { return RenderClientObjects()(_val, ctx); }
        void render(Controller::cConsoleContext& ctx)           { return RenderClientObjects()(_val, ctx); }
        void render(Controller::cDevNullContext& ctx)           { return RenderClientObjects()(_val, ctx); }

        T _val;
    };

    std::unique_ptr<IPolymorphicRenderable> _erased;
};

int main()
{
    Controller::QtOpenGL::cQOpenGLContext glContext;
    Controller::cConsoleContext           console;
    Controller::cDevNullContext           devnull;

    std::cout << "// Fully virtual dispatch\n";
    std::cout << "//\n";

    IClientObject obj = Model::ClientModel::cClientVerticesObject();
    obj.render(glContext);
    obj.render(console);
    obj.render(devnull);
    //
    obj = Model::ClientModel::cRawClientObject();
    obj.render(glContext);
    obj.render(console);
    obj.render(devnull);
    //
    obj = Model::ClientModel::cFunkyClientObject();
    obj.render(glContext);
    obj.render(console);
    obj.render(devnull);
}

Output:

clang++ -std=c++11 -Os -Wall -pedantic main.cpp && ./a.out
// Fully virtual dispatch
//
cQOpenGLContext:    N5Model11ClientModel21cClientVerticesObjectE
NOT IMPLEMENTED:    static void UserTypeHooks::RenderClientObjectsImpl<Model::ClientModel::cClientVerticesObject, Controller::cConsoleContext, void>::call(const ClientObject &, const Context &) [ClientObject = Model::ClientModel::cClientVerticesObject, Context = Controller::cConsoleContext, Enable = void]
devnull:        N5Model11ClientModel21cClientVerticesObjectE
cQOpenGLContext:    N5Model11ClientModel16cRawClientObjectE
NOT IMPLEMENTED:    static void UserTypeHooks::RenderClientObjectsImpl<Model::ClientModel::cRawClientObject, Controller::cConsoleContext, void>::call(const ClientObject &, const Context &) [ClientObject = Model::ClientModel::cRawClientObject, Context = Controller::cConsoleContext, Enable = void]
devnull:        N5Model11ClientModel16cRawClientObjectE
cQOpenGLContext:    N5Model11ClientModel18cFunkyClientObjectE
This type has cConsoleContext support due to the supports_console_ctx trait! N5Model11ClientModel18cFunkyClientObjectE
devnull:        N5Model11ClientModel18cFunkyClientObjectE

[1] (I've made sure that the client objects are not assumed to be copyable for this sample, but in fact, you might want to use ClientObjectVariant as the value type throughout more of your library)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top