phoenix-firestorm/indra/llcommon/lldoubledispatch.h

327 lines
15 KiB
C++

/**
* @file lldoubledispatch.h
* @author Nat Goodspeed
* @date 2008-11-11
* @brief function calls virtual on more than one parameter
*
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#if ! defined(LL_LLDOUBLEDISPATCH_H)
#define LL_LLDOUBLEDISPATCH_H
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
/**
* This class supports function calls which are virtual on the dynamic type of
* more than one parameter. Specifically, we address a limited but useful
* subset of that problem: function calls which accept two parameters, and
* select which particular function to call depending on the dynamic type of
* both.
*
* Scott Meyers, in More Effective C++ (Item 31), talks about some of the perils
* and pitfalls lurking down this pathway. He discusses and dismisses the
* straightforward approaches of using single-dispatch virtual functions twice,
* and of using a family of single-dispatch virtual functions which each examine
* RTTI for their other parameter. He advocates using a registry in which you
* look up the actual types of both parameters (he uses the classes' string names,
* via typeid(param).name()) to obtain a pointer to a free (non-member) function
* that will accept this pair of parameters.
*
* He does point out that his approach doesn't handle inheritance. If you have a
* registry entry for SpaceShip, and you have in hand a MilitaryShip (subclass of
* SpaceShip) and an Asteroid, you'd like to call the function appropriate for
* SpaceShips and Asteroids -- but alas, his lookup fails because the class name
* for your MilitaryShip subclass isn't in the registry.
*
* This class extends his idea to build a registry whose entries can examine the
* dynamic type of the parameter in a more flexible way -- using dynamic_cast<>
* -- thereby supporting inheritance relationships.
*
* Of course we must allow for the ambiguity this permits. We choose to use a
* sequence container rather than a map, and require that the client code
* specify the order in which dispatch-table entries should be searched. The
* result resembles the semantics of the catch clauses for a try/catch block:
* you must code catch clauses in decreasing order of specificity, because if
* you catch ErrorBaseClass before you catch ErrorSubclass, then any
* ErrorSubclass exceptions thrown by the protected code will always match
* ErrorBaseClass, and you will never reach your catch(ErrorSubclass) clause.
*
* So, in a similar way, if you have a specific routine to process
* MilitaryShip and Asteroid, you'd better place that in the table @em before
* your more general routine that processes SpaceShip and Asteroid, or else
* the MilitaryShip variant will never be called.
*
* @todo This implementation doesn't attempt to deal with
* <tt>const</tt>-correctness of arguments. Our container stores templated
* objects into which the specific parameter types have been "frozen." But to
* store all these in the same container, they are all instances of a base
* class with a virtual invocation method. Naturally the base-class virtual
* method definition cannot know the <tt>const</tt>-ness of the particular
* types with which its template subclass is instantiated.
*
* One is tempted to suggest four different virtual methods, one for each
* combination of @c const and non-<tt>const</tt> arguments. Then the client
* will select the particular virtual method that best fits the
* <tt>const</tt>-ness of the arguments in hand. The trouble with that idea is
* that in order to instantiate the subclass instance, we must compile all
* four of its virtual method overrides, which means we must be prepared to
* pass all four combinations of @c const and non-<tt>const</tt> arguments to
* the registered callable. That only works when the registered callable
* accepts both parameters as @c const.
*
* Of course the virtual method overrides could use @c const_cast to force
* them to compile correctly regardless of the <tt>const</tt>-ness of the
* registered callable's parameter declarations. But if we're going to force
* the issue with @c const_cast anyway, why bother with the four different
* virtual methods? Why not just require canonical parameter
* <tt>const</tt>-ness for any callables used with this mechanism?
*
* We therefore require that your callable accept both params as
* non-<tt>const</tt>. (This is more general than @c const: you can perform @c
* const operations on a non-<tt>const</tt> parameter, but you can't perform
* non-<tt>const</tt> operations on a @c const parameter.)
*
* For ease of use, though, our invocation method accepts both params as @c
* const. Again, you can pass a non-<tt>const</tt> object to a @c const param,
* but not the other way around. We take care of the @c const_cast for you.
*/
// LLDoubleDispatch is a template because we have to assume that all parameter
// types are subclasses of some common base class -- but we don't have to
// impose that base class on client code. Instead, we let IT tell US the
// common parameter base class.
template<class ReturnType, class ParamBaseType>
class LLDoubleDispatch
{
typedef LLDoubleDispatch<ReturnType, ParamBaseType> self_type;
public:
LLDoubleDispatch() {}
/**
* Call the first matching entry. If there's no registered Functor
* appropriate for this pair of parameter types, this call will do
* @em nothing! (If you want notification in this case, simply add a new
* Functor for (ParamBaseType&, ParamBaseType&) at the end of the table.
* The two base-class entries should match anything that isn't matched by
* any more specific entry.)
*
* See note in class documentation about <tt>const</tt>-correctness.
*/
inline
ReturnType operator()(const ParamBaseType& param1, const ParamBaseType& param2) const;
// Borrow a trick from Alexandrescu: use a Type class to "wrap" a type
// for purposes of passing the type itself into a template method.
template<typename T>
struct Type {};
/**
* Add a new Entry for a given @a Functor. As mentioned above, the order
* in which you add these entries is very important.
*
* If you want symmetrical entries -- that is, if a B and an A can call
* the same Functor as an A and a B -- then pass @c true for the last
* parameter, and we'll add a (B, A) entry as well as an (A, B) entry. But
* your @a Functor can still be written to expect exactly the pair of types
* you've explicitly specified, because the Entry with the reversed params
* will call a special thunk that swaps params before calling your @a
* Functor.
*/
template<typename Type1, typename Type2, class Functor>
void add(const Type<Type1>& t1, const Type<Type2>& t2, Functor func, bool symmetrical=false)
{
insert(t1, t2, func);
if (symmetrical)
{
// Use boost::bind() to construct a param-swapping thunk. Don't
// forget to reverse the parameters too.
insert(t2, t1, boost::bind(func, _2, _1));
}
}
/**
* Add a new Entry for a given @a Functor, explicitly passing instances of
* the Functor's leaf param types to help us figure out where to insert.
* Because it can use RTTI, this add() method isn't order-sensitive like
* the other one.
*
* If you want symmetrical entries -- that is, if a B and an A can call
* the same Functor as an A and a B -- then pass @c true for the last
* parameter, and we'll add a (B, A) entry as well as an (A, B) entry. But
* your @a Functor can still be written to expect exactly the pair of types
* you've explicitly specified, because the Entry with the reversed params
* will call a special thunk that swaps params before calling your @a
* Functor.
*/
template <typename Type1, typename Type2, class Functor>
void add(const Type1& prototype1, const Type2& prototype2, Functor func, bool symmetrical=false)
{
// Because we expect our caller to pass leaf param types, we can just
// perform an ordinary search to find the first matching iterator. If
// we find an existing Entry that matches both params, either the
// param types are the same, or the existing Entry uses the base class
// for one or both params, and the new Entry must precede that. Assume
// our client won't register two callables with exactly the SAME set
// of types; in that case we'll insert the new one before any earlier
// ones, meaning the last one registered will "win." Note that if
// find() doesn't find any matching Entry, it will return end(),
// meaning the new Entry will be last, which is fine.
typename DispatchTable::iterator insertion = find(prototype1, prototype2);
insert(Type<Type1>(), Type<Type2>(), func, insertion);
if (symmetrical)
{
insert(Type<Type2>(), Type<Type1>(), boost::bind(func, _2, _1), insertion);
}
}
/**
* Add a new Entry for a given @a Functor, specifying the Functor's leaf
* param types as explicit template arguments. This will instantiate
* temporary objects of each of these types, which requires that they have
* a lightweight default constructor.
*
* If you want symmetrical entries -- that is, if a B and an A can call
* the same Functor as an A and a B -- then pass @c true for the last
* parameter, and we'll add a (B, A) entry as well as an (A, B) entry. But
* your @a Functor can still be written to expect exactly the pair of types
* you've explicitly specified, because the Entry with the reversed params
* will call a special thunk that swaps params before calling your @a
* Functor.
*/
template <typename Type1, typename Type2, class Functor>
void add(Functor func, bool symmetrical=false)
{
// This is a convenience wrapper for the add() variant taking explicit
// instances.
add(Type1(), Type2(), func, symmetrical);
}
private:
/// This is the base class for each entry in our dispatch table.
struct EntryBase
{
virtual ~EntryBase() {}
virtual bool matches(const ParamBaseType& param1, const ParamBaseType& param2) const = 0;
virtual ReturnType operator()(ParamBaseType& param1, ParamBaseType& param2) const = 0;
};
/// Here's the template subclass that actually creates each entry.
template<typename Type1, typename Type2, class Functor>
class Entry: public EntryBase
{
public:
Entry(Functor func): mFunc(func) {}
/// Is this entry appropriate for these arguments?
virtual bool matches(const ParamBaseType& param1, const ParamBaseType& param2) const
{
return (dynamic_cast<const Type1*>(&param1) &&
dynamic_cast<const Type2*>(&param2));
}
/// invocation
virtual ReturnType operator()(ParamBaseType& param1, ParamBaseType& param2) const
{
// We perform the downcast so callable can accept leaf param
// types, instead of accepting ParamBaseType and downcasting
// explicitly.
return mFunc(dynamic_cast<Type1&>(param1), dynamic_cast<Type2&>(param2));
}
private:
/// Bind whatever function or function object the instantiator passed.
Functor mFunc;
};
/// shared_ptr manages Entry lifespan for us
typedef std::shared_ptr<EntryBase> EntryPtr;
/// use a @c list to make it easy to insert
typedef std::list<EntryPtr> DispatchTable;
DispatchTable mDispatch;
/// Look up the location of the first matching entry.
typename DispatchTable::const_iterator find(const ParamBaseType& param1, const ParamBaseType& param2) const
{
// We assert that it's safe to call the non-const find() method on a
// const LLDoubleDispatch instance. Cast away the const-ness of 'this'.
return const_cast<self_type*>(this)->find(param1, param2);
}
/// Look up the location of the first matching entry.
typename DispatchTable::iterator find(const ParamBaseType& param1, const ParamBaseType& param2)
{
return std::find_if(mDispatch.begin(), mDispatch.end(),
boost::bind(&EntryBase::matches, _1,
boost::ref(param1), boost::ref(param2)));
}
/// Look up the first matching entry.
EntryPtr lookup(const ParamBaseType& param1, const ParamBaseType& param2) const
{
typename DispatchTable::const_iterator found = find(param1, param2);
if (found != mDispatch.end())
{
// Dereferencing the list iterator gets us an EntryPtr
return *found;
}
// not found
return EntryPtr();
}
// Break out the actual insert operation so the public add() template
// function can avoid calling itself recursively. See add() comments.
template<typename Type1, typename Type2, class Functor>
void insert(const Type<Type1>& param1, const Type<Type2>& param2, Functor func)
{
insert(param1, param2, func, mDispatch.end());
}
// Break out the actual insert operation so the public add() template
// function can avoid calling itself recursively. See add() comments.
template<typename Type1, typename Type2, class Functor>
void insert(const Type<Type1>&, const Type<Type2>&, Functor func,
typename DispatchTable::iterator where)
{
mDispatch.insert(where, EntryPtr(new Entry<Type1, Type2, Functor>(func)));
}
/// Don't implement the copy ctor. Everyone will be happier if the
/// LLDoubleDispatch object isn't copied.
LLDoubleDispatch(const LLDoubleDispatch& src);
};
template <class ReturnType, class ParamBaseType>
ReturnType LLDoubleDispatch<ReturnType, ParamBaseType>::operator()(const ParamBaseType& param1,
const ParamBaseType& param2) const
{
EntryPtr found = lookup(param1, param2);
if (found.get() == 0)
return ReturnType(); // bogus return value
// otherwise, call the Functor we found
return (*found)(const_cast<ParamBaseType&>(param1), const_cast<ParamBaseType&>(param2));
}
#endif /* ! defined(LL_LLDOUBLEDISPATCH_H) */