PageRenderTime 108ms CodeModel.GetById 64ms app.highlight 35ms RepoModel.GetById 2ms app.codeStats 0ms

/indra/llcommon/lldoubledispatch.h

https://bitbucket.org/lindenlab/viewer-beta/
C++ Header | 326 lines | 110 code | 21 blank | 195 comment | 6 complexity | 8df7782d89d097fd7688b114512c7be2 MD5 | raw file
  1/**
  2 * @file   lldoubledispatch.h
  3 * @author Nat Goodspeed
  4 * @date   2008-11-11
  5 * @brief  function calls virtual on more than one parameter
  6 * 
  7 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
  8 * Second Life Viewer Source Code
  9 * Copyright (C) 2010, Linden Research, Inc.
 10 * 
 11 * This library is free software; you can redistribute it and/or
 12 * modify it under the terms of the GNU Lesser General Public
 13 * License as published by the Free Software Foundation;
 14 * version 2.1 of the License only.
 15 * 
 16 * This library is distributed in the hope that it will be useful,
 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19 * Lesser General Public License for more details.
 20 * 
 21 * You should have received a copy of the GNU Lesser General Public
 22 * License along with this library; if not, write to the Free Software
 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 24 * 
 25 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 26 * $/LicenseInfo$
 27 */
 28
 29#if ! defined(LL_LLDOUBLEDISPATCH_H)
 30#define LL_LLDOUBLEDISPATCH_H
 31
 32#include <list>
 33#include <boost/shared_ptr.hpp>
 34#include <boost/function.hpp>
 35#include <boost/bind.hpp>
 36#include <boost/ref.hpp>
 37
 38/**
 39 * This class supports function calls which are virtual on the dynamic type of
 40 * more than one parameter. Specifically, we address a limited but useful
 41 * subset of that problem: function calls which accept two parameters, and
 42 * select which particular function to call depending on the dynamic type of
 43 * both.
 44 * 
 45 * Scott Meyers, in More Effective C++ (Item 31), talks about some of the perils
 46 * and pitfalls lurking down this pathway.  He discusses and dismisses the
 47 * straightforward approaches of using single-dispatch virtual functions twice,
 48 * and of using a family of single-dispatch virtual functions which each examine
 49 * RTTI for their other parameter.  He advocates using a registry in which you
 50 * look up the actual types of both parameters (he uses the classes' string names,
 51 * via typeid(param).name()) to obtain a pointer to a free (non-member) function
 52 * that will accept this pair of parameters.
 53 * 
 54 * He does point out that his approach doesn't handle inheritance.  If you have a
 55 * registry entry for SpaceShip, and you have in hand a MilitaryShip (subclass of
 56 * SpaceShip) and an Asteroid, you'd like to call the function appropriate for
 57 * SpaceShips and Asteroids -- but alas, his lookup fails because the class name
 58 * for your MilitaryShip subclass isn't in the registry.
 59 * 
 60 * This class extends his idea to build a registry whose entries can examine the
 61 * dynamic type of the parameter in a more flexible way -- using dynamic_cast<>
 62 * -- thereby supporting inheritance relationships.
 63 * 
 64 * Of course we must allow for the ambiguity this permits. We choose to use a
 65 * sequence container rather than a map, and require that the client code
 66 * specify the order in which dispatch-table entries should be searched. The
 67 * result resembles the semantics of the catch clauses for a try/catch block:
 68 * you must code catch clauses in decreasing order of specificity, because if
 69 * you catch ErrorBaseClass before you catch ErrorSubclass, then any
 70 * ErrorSubclass exceptions thrown by the protected code will always match
 71 * ErrorBaseClass, and you will never reach your catch(ErrorSubclass) clause.
 72 * 
 73 * So, in a similar way, if you have a specific routine to process
 74 * MilitaryShip and Asteroid, you'd better place that in the table @em before
 75 * your more general routine that processes SpaceShip and Asteroid, or else
 76 * the MilitaryShip variant will never be called.
 77 *
 78 * @todo This implementation doesn't attempt to deal with
 79 * <tt>const</tt>-correctness of arguments. Our container stores templated
 80 * objects into which the specific parameter types have been "frozen." But to
 81 * store all these in the same container, they are all instances of a base
 82 * class with a virtual invocation method. Naturally the base-class virtual
 83 * method definition cannot know the <tt>const</tt>-ness of the particular
 84 * types with which its template subclass is instantiated.
 85 *
 86 * One is tempted to suggest four different virtual methods, one for each
 87 * combination of @c const and non-<tt>const</tt> arguments. Then the client
 88 * will select the particular virtual method that best fits the
 89 * <tt>const</tt>-ness of the arguments in hand. The trouble with that idea is
 90 * that in order to instantiate the subclass instance, we must compile all
 91 * four of its virtual method overrides, which means we must be prepared to
 92 * pass all four combinations of @c const and non-<tt>const</tt> arguments to
 93 * the registered callable. That only works when the registered callable
 94 * accepts both parameters as @c const.
 95 *
 96 * Of course the virtual method overrides could use @c const_cast to force
 97 * them to compile correctly regardless of the <tt>const</tt>-ness of the
 98 * registered callable's parameter declarations. But if we're going to force
 99 * the issue with @c const_cast anyway, why bother with the four different
100 * virtual methods? Why not just require canonical parameter
101 * <tt>const</tt>-ness for any callables used with this mechanism?
102 *
103 * We therefore require that your callable accept both params as
104 * non-<tt>const</tt>. (This is more general than @c const: you can perform @c
105 * const operations on a non-<tt>const</tt> parameter, but you can't perform
106 * non-<tt>const</tt> operations on a @c const parameter.)
107 *
108 * For ease of use, though, our invocation method accepts both params as @c
109 * const. Again, you can pass a non-<tt>const</tt> object to a @c const param,
110 * but not the other way around. We take care of the @c const_cast for you.
111 */
112// LLDoubleDispatch is a template because we have to assume that all parameter
113// types are subclasses of some common base class -- but we don't have to
114// impose that base class on client code.  Instead, we let IT tell US the
115// common parameter base class.
116template<class ReturnType, class ParamBaseType>
117class LLDoubleDispatch
118{
119    typedef LLDoubleDispatch<ReturnType, ParamBaseType> self_type;
120
121public:
122    LLDoubleDispatch() {}
123
124    /**
125     * Call the first matching entry.  If there's no registered Functor
126     * appropriate for this pair of parameter types, this call will do
127     * @em nothing!  (If you want notification in this case, simply add a new
128     * Functor for (ParamBaseType&, ParamBaseType&) at the end of the table.
129     * The two base-class entries should match anything that isn't matched by
130     * any more specific entry.)
131     *
132     * See note in class documentation about <tt>const</tt>-correctness.
133     */
134    inline
135    ReturnType operator()(const ParamBaseType& param1, const ParamBaseType& param2) const;
136
137    // Borrow a trick from Alexandrescu:  use a Type class to "wrap" a type
138    // for purposes of passing the type itself into a template method.
139    template<typename T>
140    struct Type {};
141
142    /**
143     * Add a new Entry for a given @a Functor. As mentioned above, the order
144     * in which you add these entries is very important.
145     *
146     * If you want symmetrical entries -- that is, if a B and an A can call
147     * the same Functor as an A and a B -- then pass @c true for the last
148     * parameter, and we'll add a (B, A) entry as well as an (A, B) entry. But
149     * your @a Functor can still be written to expect exactly the pair of types
150     * you've explicitly specified, because the Entry with the reversed params
151     * will call a special thunk that swaps params before calling your @a
152     * Functor.
153     */
154    template<typename Type1, typename Type2, class Functor>
155    void add(const Type<Type1>& t1, const Type<Type2>& t2, Functor func, bool symmetrical=false)
156    {
157        insert(t1, t2, func);
158        if (symmetrical)
159        {
160            // Use boost::bind() to construct a param-swapping thunk. Don't
161            // forget to reverse the parameters too.
162            insert(t2, t1, boost::bind(func, _2, _1));
163        }
164    }
165
166    /**
167     * Add a new Entry for a given @a Functor, explicitly passing instances of
168     * the Functor's leaf param types to help us figure out where to insert.
169     * Because it can use RTTI, this add() method isn't order-sensitive like
170     * the other one.
171     *
172     * If you want symmetrical entries -- that is, if a B and an A can call
173     * the same Functor as an A and a B -- then pass @c true for the last
174     * parameter, and we'll add a (B, A) entry as well as an (A, B) entry. But
175     * your @a Functor can still be written to expect exactly the pair of types
176     * you've explicitly specified, because the Entry with the reversed params
177     * will call a special thunk that swaps params before calling your @a
178     * Functor.
179     */
180    template <typename Type1, typename Type2, class Functor>
181    void add(const Type1& prototype1, const Type2& prototype2, Functor func, bool symmetrical=false)
182    {
183        // Because we expect our caller to pass leaf param types, we can just
184        // perform an ordinary search to find the first matching iterator. If
185        // we find an existing Entry that matches both params, either the
186        // param types are the same, or the existing Entry uses the base class
187        // for one or both params, and the new Entry must precede that. Assume
188        // our client won't register two callables with exactly the SAME set
189        // of types; in that case we'll insert the new one before any earlier
190        // ones, meaning the last one registered will "win." Note that if
191        // find() doesn't find any matching Entry, it will return end(),
192        // meaning the new Entry will be last, which is fine.
193        typename DispatchTable::iterator insertion = find(prototype1, prototype2);
194        insert(Type<Type1>(), Type<Type2>(), func, insertion);
195        if (symmetrical)
196        {
197            insert(Type<Type2>(), Type<Type1>(), boost::bind(func, _2, _1), insertion);
198        }
199    }
200
201    /**
202     * Add a new Entry for a given @a Functor, specifying the Functor's leaf
203     * param types as explicit template arguments. This will instantiate
204     * temporary objects of each of these types, which requires that they have
205     * a lightweight default constructor.
206     *
207     * If you want symmetrical entries -- that is, if a B and an A can call
208     * the same Functor as an A and a B -- then pass @c true for the last
209     * parameter, and we'll add a (B, A) entry as well as an (A, B) entry. But
210     * your @a Functor can still be written to expect exactly the pair of types
211     * you've explicitly specified, because the Entry with the reversed params
212     * will call a special thunk that swaps params before calling your @a
213     * Functor.
214     */
215    template <typename Type1, typename Type2, class Functor>
216    void add(Functor func, bool symmetrical=false)
217    {
218        // This is a convenience wrapper for the add() variant taking explicit
219        // instances.
220        add(Type1(), Type2(), func, symmetrical);
221    }
222
223private:
224    /// This is the base class for each entry in our dispatch table.
225    struct EntryBase
226    {
227        virtual ~EntryBase() {}
228        virtual bool matches(const ParamBaseType& param1, const ParamBaseType& param2) const = 0;
229        virtual ReturnType operator()(ParamBaseType& param1, ParamBaseType& param2) const = 0;
230    };
231
232    /// Here's the template subclass that actually creates each entry.
233    template<typename Type1, typename Type2, class Functor>
234    class Entry: public EntryBase
235    {
236    public:
237        Entry(Functor func): mFunc(func) {}
238        /// Is this entry appropriate for these arguments?
239        virtual bool matches(const ParamBaseType& param1, const ParamBaseType& param2) const
240        {
241            return (dynamic_cast<const Type1*>(&param1) &&
242                    dynamic_cast<const Type2*>(&param2));
243        }
244        /// invocation
245        virtual ReturnType operator()(ParamBaseType& param1, ParamBaseType& param2) const
246        {
247            // We perform the downcast so callable can accept leaf param
248            // types, instead of accepting ParamBaseType and downcasting
249            // explicitly.
250            return mFunc(dynamic_cast<Type1&>(param1), dynamic_cast<Type2&>(param2));
251        }
252    private:
253        /// Bind whatever function or function object the instantiator passed.
254        Functor mFunc;
255    };
256
257    /// shared_ptr manages Entry lifespan for us
258    typedef boost::shared_ptr<EntryBase> EntryPtr;
259    /// use a @c list to make it easy to insert
260    typedef std::list<EntryPtr> DispatchTable;
261    DispatchTable mDispatch;
262
263    /// Look up the location of the first matching entry.
264    typename DispatchTable::const_iterator find(const ParamBaseType& param1, const ParamBaseType& param2) const
265    {
266        // We assert that it's safe to call the non-const find() method on a
267        // const LLDoubleDispatch instance. Cast away the const-ness of 'this'.
268        return const_cast<self_type*>(this)->find(param1, param2);
269    }
270
271    /// Look up the location of the first matching entry.
272    typename DispatchTable::iterator find(const ParamBaseType& param1, const ParamBaseType& param2)
273    {
274        return std::find_if(mDispatch.begin(), mDispatch.end(),
275                            boost::bind(&EntryBase::matches, _1,
276                                        boost::ref(param1), boost::ref(param2)));
277    }
278
279    /// Look up the first matching entry.
280    EntryPtr lookup(const ParamBaseType& param1, const ParamBaseType& param2) const
281    {
282        typename DispatchTable::const_iterator found = find(param1, param2);            
283        if (found != mDispatch.end())
284        {
285            // Dereferencing the list iterator gets us an EntryPtr
286            return *found;
287        }
288        // not found
289        return EntryPtr();
290    }
291
292    // Break out the actual insert operation so the public add() template
293    // function can avoid calling itself recursively.  See add() comments.
294    template<typename Type1, typename Type2, class Functor>
295    void insert(const Type<Type1>& param1, const Type<Type2>& param2, Functor func)
296    {
297        insert(param1, param2, func, mDispatch.end());
298    }
299
300    // Break out the actual insert operation so the public add() template
301    // function can avoid calling itself recursively.  See add() comments.
302    template<typename Type1, typename Type2, class Functor>
303    void insert(const Type<Type1>&, const Type<Type2>&, Functor func,
304                typename DispatchTable::iterator where)
305    {
306        mDispatch.insert(where, EntryPtr(new Entry<Type1, Type2, Functor>(func)));
307    }
308
309    /// Don't implement the copy ctor.  Everyone will be happier if the
310    /// LLDoubleDispatch object isn't copied.
311    LLDoubleDispatch(const LLDoubleDispatch& src);
312};
313
314template <class ReturnType, class ParamBaseType>
315ReturnType LLDoubleDispatch<ReturnType, ParamBaseType>::operator()(const ParamBaseType& param1,
316                                                                   const ParamBaseType& param2) const
317{
318    EntryPtr found = lookup(param1, param2);
319    if (found.get() == 0)
320        return ReturnType();    // bogus return value
321
322    // otherwise, call the Functor we found
323    return (*found)(const_cast<ParamBaseType&>(param1), const_cast<ParamBaseType&>(param2));
324}
325
326#endif /* ! defined(LL_LLDOUBLEDISPATCH_H) */