2

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.

cguenther
  • 1,579
  • 1
  • 10
  • 14
  • Does the binary version of mpl `transform` help? http://www.boost.org/doc/libs/1_55_0/libs/mpl/doc/refmanual/transform.html – sehe Jan 31 '14 at 15:29
  • 1
    `base` can become a template with your new `mpl::vector` as a parmeter, then you can have another fold within base that fold over that template parameter which is the `contextTypes`. It will give you a `fold` inside a `fold` which is what you need. – Drax Jan 31 '14 at 15:34
  • What are you trying to achieve here? It looks like you're trying to get multimethod semantics. Do you want this to be usable as an extension point? Do you just want to implement the method for /some/ combinations? Or should users of your library be able to provide specializations for the render methods? I think there are much more elegant ways depending on what you want to achieve. – sehe Feb 01 '14 at 00:20
  • I've turned my comment into an answer, so I can explain the idea better. I hope you don't mind my answering this question with a wholly different approach. It might just give you some useful ideas, if indeed this what you're trying to achieve. – sehe Feb 01 '14 at 01:16

1 Answers1

1

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)

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I just realized my answer missed a point of your question where you wanted the second varying argument type to be the context object. The technique is still very applicable: http://coliru.stacked-crooked.com/a/98b33b2d11ed4f9e (which adds `console` and `devnull` contexts. You'd probably think of DirectX, SDL contexts :)) – sehe Feb 01 '14 at 03:15
  • Oh dear, you got exactly what i was try to achieve and present a very smart way to do that. Thank you very much, but it seems that i need more knowledge about boost variants and type traits to understand this implementation in detail. Do you know about good tutorials for these topics? – cguenther Feb 01 '14 at 14:39
  • I guess doing hands on work is the best way. I learned the utility libraries (like optional, fusion, variant) by osmosis when learning boost spirit. Let me see whether I can find some resources that have been brought to my attention in the past. – sehe Feb 01 '14 at 14:43
  • Oh. Do have a look at the recent Boost Type Erasure library. It plays out in the same area, I think – sehe Feb 01 '14 at 14:45
  • Do you see a good method to reach dynamic polymorphism by calling that function? For example: multimethod(basePtrObject, basePtrContext); which then automatically calls a defined specialized template method of the derivedObject and derivedContext. – cguenther Feb 03 '14 at 16:43
  • 1
    Yes, in principle. Though I'm not completely sure why you'd need /both/ static and dynamic polymorphism? This is where the type erasure comes in. Let me see what I can come up with later – sehe Feb 03 '14 at 16:46
  • Here's the last demo updated for virtual invocation - retaining the out-of-class customization point `UserTypeHooks::RenderClientObjectsImpl`: **[coliru.stacked-crooked.com/a/e88572fdda9eadf4](http://coliru.stacked-crooked.com/a/e88572fdda9eadf4)**. Note I stay with value-semantics (even though it's heap-based now) - because I strongly believe in that for /simplicity/. And if your ClientObjects _aren't_ movable, you'll want to add some kind of `emplace-constructor` (`make_clientobject(...)`) :) – sehe Feb 03 '14 at 23:30