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