Update此后我提供了一个更新版本,它确实更符合您的问题(通过在客户端对象上分派)and和上下文)。看住在科里鲁 http://coliru.stacked-crooked.com/a/98b33b2d11ed4f9e.
Update 2发布了一个版本,将类型擦除添加到组合中以获得虚拟接口,同时保留其他优点。看住在科里鲁 http://coliru.stacked-crooked.com/a/e88572fdda9eadf4将其发布在底部以确保将来保留在 SO 上。
我正在将我的评论转换为答案,因为它提供了更多的空间来阐述。
我的印象是你“只是”试图获得像语义这样的多方法(即具有依赖于多个对象类型的多态行为的函数)。
输入演员
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;
让我们看看这些物品的故事是如何展开的:)
静态调度;从基础开始
在这种情况下,我怀疑普通的多态函子可能更切题:
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;
};
您可以像任何可调用对象一样使用它,依赖于静态调度:
RenderClientObjects multimethod(&glContext);
multimethod(vertices, vertices);
multimethod(vertices, funky);
multimethod(raw, vertices);
multimethod(funky, vertices);
运行时调度:输入boost::variant
!
跳过如何的问题// some implementation
将提供,让我跳到它的优雅之处:你可以神奇地使用运行时二进制调度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);
}
提供(自定义)实现
当然,您仍然希望为这些重载提供实现。你可以
- 提供显式重载
- 委托给可以专门化的实现类(有时称为定制点, 扩展点 or 用户定义的钩子 etc.)
- 的组合
完整样本
这是使用提到的所有方法的完整示例and显示详尽的静态和运行时调度。
See it 住在科里鲁 http://coliru.stacked-crooked.com/a/cd100255cefdfd05
#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);
}
哦,为了完整起见,这是输出:
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.
类型擦除的接口版本
完整代码 '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)