proxygen
proxygen/folly/folly/docs/Poly.md
Go to the documentation of this file.
1 `folly/Poly.h`
2 -------------------------------
3 
4 `Poly` is a class template that makes it relatively easy to define a
5 type-erasing polymorphic object wrapper.
6 
7 ### Type-erasure
8 ***
9 
10 `std::function` is one example of a type-erasing polymorphic object wrapper;
11 `folly::exception_wrapper` is another. Type-erasure is often used as an
12 alternative to dynamic polymorphism via inheritance-based virtual dispatch.
13 The distinguishing characteristic of type-erasing wrappers are:
14 
15 * **Duck typing:** Types do not need to inherit from an abstract base
16  class in order to be assignable to a type-erasing wrapper; they merely
17  need to satisfy a particular interface.
18 * **Value semantics:** Type-erasing wrappers are objects that can be
19  passed around _by value_. This is in contrast to abstract base classes
20  which must be passed by reference or by pointer or else suffer from
21  _slicing_, which causes them to lose their polymorphic behaviors.
22  Reference semantics make it difficult to reason locally about code.
23 * **Automatic memory management:** When dealing with inheritance-based
24  dynamic polymorphism, it is often necessary to allocate and manage
25  objects on the heap. This leads to a proliferation of `shared_ptr`s and
26  `unique_ptr`s in APIs, complicating their point-of-use. APIs that take
27  type-erasing wrappers, on the other hand, can often store small objects
28  in-situ, with no dynamic allocation. The memory management, if any, is
29  handled for you, and leads to cleaner APIs: consumers of your API don't
30  need to pass `shared_ptr<AbstractBase>`; they can simply pass any object
31  that satisfies the interface you require. (`std::function` is a
32  particularly compelling example of this benefit. Far worse would be an
33  inheritance-based callable solution like
34  `shared_ptr<ICallable<void(int)>>`. )
35 
36 ### Examples: Defining a type-erasing function wrapper with `folly::Poly`
37 ***
38 
39 Defining a polymorphic wrapper with `Poly` is a matter of defining two
40 things:
41 
42 * An *interface*, consisting of public member functions, and
43 * A *mapping* from a concrete type to a set of member function bindings.
44 
45 Below is a simple example program that defines a `drawable` wrapper for any type
46 that provides a `draw` member function. (The details will be explained later.)
47 
48 ``` Cpp
49  // This example is an adaptation of one found in Louis Dionne's dyno library.
50  #include <folly/Poly.h>
51  #include <iostream>
52 
53  struct IDrawable {
54  // Define the interface of something that can be drawn:
55  template <class Base> struct Interface : Base {
56  void draw(std::ostream& out) const { folly::poly_call<0>(*this, out);}
57  };
58  // Define how concrete types can fulfill that interface (in C++17):
59  template <class T> using Members = folly::PolyMembers<&T::draw>;
60  };
61 
62  // Define an object that can hold anything that can be drawn:
63  using drawable = folly::Poly<IDrawable>;
64 
65  struct Square {
66  void draw(std::ostream& out) const { out << "Square\n"; }
67  };
68 
69  struct Circle {
70  void draw(std::ostream& out) const { out << "Circle\n"; }
71  };
72 
73  void f(drawable const& d) {
74  d.draw(std::cout);
75  }
76 
77  int main() {
78  f(Square{}); // prints Square
79  f(Circle{}); // prints Circle
80  }
81 ```
82 
83 The above program prints:
84 
85 ```
86  Square
87  Circle
88 ```
89 
90 Here is another (heavily commented) example of a simple implementation of a
91 `std::function`-like polymorphic wrapper. Its interface has only a single
92 member function: `operator()`
93 
94 ``` Cpp
95  // An interface for a callable object of a particular signature, Fun
96  // (most interfaces don't need to be templates, FWIW).
97  template <class Fun>
98  struct IFunction;
99 
100  template <class R, class... As>
101  struct IFunction<R(As...)> {
102  // An interface is defined as a nested class template called
103  // Interface that takes a single template parameter, Base, from
104  // which it inherits.
105  template <class Base>
106  struct Interface : Base {
107  // The Interface has public member functions. These become the
108  // public interface of the resulting Poly instantiation.
109  // (Implementation note: Poly<IFunction<Sig>> will publicly
110  // inherit from this struct, which is what gives it the right
111  // member functions.)
112  R operator()(As... as) const {
113  // The definition of each member function in your interface will
114  // always consist of a single line dispatching to folly::poly_call<N>.
115  // The "N" corresponds to the N-th member function in the
116  // list of member function bindings, Members, defined below.
117  // The first argument will always be *this, and the rest of the
118  // arguments should simply forward (if necessary) the member
119  // function's arguments.
120  return static_cast<R>(
121  folly::poly_call<0>(*this, std::forward<As>(as)...));
122  }
123  };
124  // The "Members" alias template is a comma-separated list of bound
125  // member functions for a given concrete type "T". The
126  // "FOLLY_POLY_MEMBERS" macro accepts a comma-separated list, and the
127  // (optional) "FOLLY_POLY_MEMBER" macro lets you disambiguate overloads
128  // by explicitly specifying the function signature the target member
129  // function should have. In this case, we require "T" to have a
130  // function call operator with the signature `R(As...) const`.
131  //
132  // If you are using a C++17-compatible compiler, you can do away with
133  // the macros and write this as:
134  //
135  // template <class T>
136  // using Members =
137  // folly::PolyMembers<folly::sig<R(As...) const>(&T::operator())>;
138  //
139  // And since `folly::sig` is only needed for disambiguation in case of
140  // overloads, if you are not concerned about objects with overloaded
141  // function call operators, it could be further simplified to:
142  //
143  // template <class T>
144  // using Members = folly::PolyMembers<&T::operator()>;
145  //
146  template <class T>
147  using Members = FOLLY_POLY_MEMBERS(
148  FOLLY_POLY_MEMBER(R(As...) const, &T::operator()));
149  };
150 
151  // Now that we have defined the interface, we can pass it to Poly to
152  // create our type-erasing wrapper:
153  template <class Fun>
154  using Function = Poly<IFunction<Fun>>;
155 ```
156 
157 Given the above definition of `Function`, users can now initialize instances
158 of (say) `Function<int(int, int)>` with function objects like
159 `std::plus<int>` and `std::multiplies<int>`, as below:
160 
161 ``` Cpp
162  Function<int(int, int)> fun = std::plus<int>{};
163  assert(5 == fun(2, 3));
164  fun = std::multiplies<int>{};
165  assert(6 = fun(2, 3));
166 ```
167 
168 ### Defining an interface with C++17
169 ***
170 
171 With C++17, defining an interface to be used with `Poly` is fairly
172 straightforward. As in the `Function` example above, there is a struct with
173 a nested `Interface` class template and a nested `Members` alias template.
174 No macros are needed with C++17.
175 
176 Imagine we were defining something like a Java-style iterator. If we are
177 using a C++17 compiler, our interface would look something like this:
178 
179 ``` Cpp
180  template <class Value>
181  struct IJavaIterator {
182  template <class Base>
183  struct Interface : Base {
184  bool Done() const { return folly::poly_call<0>(*this); }
185  Value Current() const { return folly::poly_call<1>(*this); }
186  void Next() { folly::poly_call<2>(*this); }
187  };
188  // NOTE: This works in C++17 only:
189  template <class T>
190  using Members = folly::PolyMembers<&T::Done, &T::Current, &T::Next>;
191  };
192 
193  template <class Value>
194  using JavaIterator = Poly<IJavaIterator<Value>>;
195 ```
196 
197 Given the above definition, `JavaIterator<int>` can be used to hold instances
198 of any type that has `Done`, `Current`, and `Next` member functions with the
199 correct (or compatible) signatures.
200 
201 The presence of overloaded member functions complicates this picture. Often,
202 property members are faked in C++ with `const` and non-`const` member
203 function overloads, like in the interface specified below:
204 
205 ``` Cpp
206  struct IIntProperty {
207  template <class Base>
208  struct Interface : Base {
209  int Value() const { return folly::poly_call<0>(*this); }
210  void Value(int i) { folly::poly_call<1>(*this, i); }
211  };
212  // NOTE: This works in C++17 only:
213  template <class T>
214  using Members = folly::PolyMembers<
215  folly::sig<int() const>(&T::Value),
216  folly::sig<void(int)>(&T::Value)>;
217  };
218 
219  using IntProperty = Poly<IIntProperty>;
220 ```
221 
222 Now, any object that has `Value` members of compatible signatures can be
223 assigned to instances of `IntProperty` object. Note how `folly::sig` is used
224 to disambiguate the overloads of `&T::Value`.
225 
226 ### Defining an interface with C++14
227 ***
228 
229 In C++14, the nice syntax above doesn't work, so we have to resort to macros.
230 The two examples above would look like this:
231 
232 ``` Cpp
233  template <class Value>
234  struct IJavaIterator {
235  template <class Base>
236  struct Interface : Base {
237  bool Done() const { return folly::poly_call<0>(*this); }
238  Value Current() const { return folly::poly_call<1>(*this); }
239  void Next() { folly::poly_call<2>(*this); }
240  };
241  // NOTE: This works in C++14 and C++17:
242  template <class T>
243  using Members = FOLLY_POLY_MEMBERS(&T::Done, &T::Current, &T::Next);
244  };
245 
246  template <class Value>
247  using JavaIterator = Poly<IJavaIterator<Value>>;
248 ```
249 
250 and
251 
252 ``` Cpp
253  struct IIntProperty {
254  template <class Base>
255  struct Interface : Base {
256  int Value() const { return folly::poly_call<0>(*this); }
257  void Value(int i) { return folly::poly_call<1>(*this, i); }
258  };
259  // NOTE: This works in C++14 and C++17:
260  template <class T>
261  using Members = FOLLY_POLY_MEMBERS(
262  FOLLY_POLY_MEMBER(int() const, &T::Value),
263  FOLLY_POLY_MEMBER(void(int), &T::Value));
264  };
265 
266  using IntProperty = Poly<IIntProperty>;
267 ```
268 
269 ### Extending interfaces
270 ***
271 
272 One typical advantage of inheritance-based solutions to runtime polymorphism
273 is that one polymorphic interface could extend another through inheritance.
274 The same can be accomplished with type-erasing polymorphic wrappers. In
275 the `Poly` library, you can use `folly::PolyExtends` to say that one interface
276 extends another.
277 
278 ``` Cpp
279  struct IFoo {
280  template <class Base>
281  struct Interface : Base {
282  void Foo() const { return folly::poly_call<0>(*this); }
283  };
284  template <class T>
285  using Members = FOLLY_POLY_MEMBERS(&T::Foo);
286  };
287 
288  // The IFooBar interface extends the IFoo interface
289  struct IFooBar : PolyExtends<IFoo> {
290  template <class Base>
291  struct Interface : Base {
292  void Bar() const { return folly::poly_call<0>(*this); }
293  };
294  template <class T>
295  using Members = FOLLY_POLY_MEMBERS(&T::Bar);
296  };
297 
298  using FooBar = Poly<IFooBar>;
299 ```
300 
301 Given the above definition, instances of type `FooBar` have both `Foo()` and
302 `Bar()` member functions.
303 
304 The sensible conversions exist between a wrapped derived type and a wrapped
305 base type. For instance, assuming `IDerived` extends `IBase` with `PolyExtends`:
306 
307 ``` Cpp
308  Poly<IDerived> derived = ...;
309  Poly<IBase> base = derived; // This conversion is OK.
310 ```
311 
312 As you would expect, there is no conversion in the other direction, and at
313 present there is no `Poly` equivalent to `dynamic_cast`.
314 
315 ### Type-erasing polymorphic reference wrappers
316 ***
317 
318 Sometimes you don't need to own a copy of an object; a reference will do. For
319 that you can use `Poly` to capture a _reference_ to an object satisfying an
320 interface rather than the whole object itself. The syntax is intuitive.
321 
322 ``` Cpp
323  int i = 42;
324 
325  // Capture a mutable reference to an object of any IRegular type:
326  Poly<IRegular &> intRef = i;
327 
328  assert(42 == folly::poly_cast<int>(intRef));
329  // Assert that we captured the address of "i":
330  assert(&i == &folly::poly_cast<int>(intRef));
331 ```
332 
333 A reference-like `Poly` has a different interface than a value-like `Poly`.
334 Rather than calling member functions with the `obj.fun()` syntax, you would
335 use the `obj->fun()` syntax. This is for the sake of `const`-correctness.
336 For example, consider the code below:
337 
338 ``` Cpp
339  struct IFoo {
340  template <class Base>
341  struct Interface {
342  void Foo() { folly::poly_call<0>(*this); }
343  };
344  template <class T>
345  using Members = folly::PolyMembers<&T::Foo>;
346  };
347 
348  struct SomeFoo {
349  void Foo() { std::printf("SomeFoo::Foo\n"); }
350  };
351 
352  SomeFoo foo;
353  Poly<IFoo &> const anyFoo = foo;
354  anyFoo->Foo(); // prints "SomeFoo::Foo"
355 ```
356 
357 Notice in the above code that the `Foo` member function is non-`const`.
358 Notice also that the `anyFoo` object is `const`. However, since it has
359 captured a non-`const` reference to the `foo` object, it should still be
360 possible to dispatch to the non-`const` `Foo` member function. When
361 instantiated with a reference type, `Poly` has an overloaded `operator->`
362 member that returns a pointer to the `IFoo` interface with the correct
363 `const`-ness, which makes this work.
364 
365 The same mechanism also prevents users from calling non-`const` member
366 functions on `Poly` objects that have captured `const` references, which
367 would violate `const`-correctness.
368 
369 Sensible conversions exist between non-reference and reference `Poly`s. For
370 instance:
371 
372 ``` Cpp
373  Poly<IRegular> value = 42;
374  Poly<IRegular &> mutable_ref = value;
375  Poly<IRegular const &> const_ref = mutable_ref;
376 
377  assert(&poly_cast<int>(value) == &poly_cast<int>(mutable_ref));
378  assert(&poly_cast<int>(value) == &poly_cast<int>(const_ref));
379 ```
380 
381 ### Non-member functions (C++17)
382 ***
383 
384 If you wanted to write the interface `ILogicallyNegatable`, which captures
385 all types that can be negated with unary `operator!`, you could do it
386 as we've shown above, by binding `&T::operator!` in the nested `Members`
387 alias template, but that has the problem that it won't work for types that
388 have defined unary `operator!` as a free function. To handle this case,
389 the `Poly` library lets you use a free function instead of a member function
390 when creating a binding.
391 
392 With C++17 you may use a lambda to create a binding, as shown in the example
393 below:
394 
395 ``` Cpp
396  struct ILogicallyNegatable {
397  template <class Base>
398  struct Interface : Base {
399  bool operator!() const { return folly::poly_call<0>(*this); }
400  };
401  template <class T>
402  using Members = folly::PolyMembers<
403  +[](T const& t) -> decltype(bool(!t)) { return bool(!t); }>;
404  };
405 ```
406 
407 This requires some explanation. The unary `operator+` in front of the lambda
408 is necessary! It causes the lambda to decay to a C-style function pointer,
409 which is one of the types that `folly::PolyMembers` accepts. The `decltype` in
410 the lambda return type is also necessary. Through the magic of SFINAE, it
411 will cause `Poly<ILogicallyNegatable>` to reject any types that don't support
412 unary `operator!`.
413 
414 If you are using a free function to create a binding, the first parameter is
415 implicitly the `this` parameter. It will receive the type-erased object.
416 
417 ### Non-member functions (C++14)
418 ***
419 
420 If you are using a C++14 compiler, the definition of `ILogicallyNegatable`
421 above will fail because lambdas are not `constexpr`. We can get the same
422 effect by writing the lambda as a named free function, as show below:
423 
424 ``` Cpp
425  struct ILogicallyNegatable {
426  template <class Base>
427  struct Interface : Base {
428  bool operator!() const { return folly::poly_call<0>(*this); }
429  };
430  template <class T>
431  static auto negate(T const& t)
432  -> decltype(bool(!t)) { return bool(!t); }
433  template <class T>
434  using Members = FOLLY_POLY_MEMBERS(&negate<T>);
435  };
436 ```
437 
438 As with the example that uses the lambda in the preceding section, the first
439 parameter is implicitly the `this` parameter. It will receive the type-erased
440 object.
441 
442 ### Multi-dispatch
443 ***
444 
445 What if you want to create an `IAddable` interface for things that can be
446 added? Adding requires _two_ objects, both of which are type-erased. This
447 interface requires dispatching on both objects, doing the addition only
448 if the types are the same. For this we make use of the `PolySelf` template
449 alias to define an interface that takes more than one object of the the
450 erased type.
451 
452 ``` Cpp
453  struct IAddable {
454  template <class Base>
455  struct Interface : Base {
456  friend PolySelf<Base>
457  operator+(PolySelf<Base> const& a, PolySelf<Base> const& b) const {
458  return folly::poly_call<0>(a, b);
459  }
460  };
461  template <class T>
462  using Members = folly::PolyMembers<
463  +[](T const& a, T const& b) -> decltype(a + b) { return a + b; }>;
464  };
465 ```
466 
467 Given the above definition of `IAddable` we would be able to do the following:
468 
469 ``` Cpp
470  Poly<IAddable> a = 2, b = 3;
471  Poly<IAddable> c = a + b;
472  assert(poly_cast<int>(c) == 5);
473 ```
474 
475 If `a` and `b` stored objects of different types, a `BadPolyCast` exception
476 would be thrown.
477 
478 ### Move-only types
479 ***
480 
481 If you want to store move-only types, then your interface should extend the
482 `poly::IMoveOnly` interface.
483 
484 ### Implementation notes
485 ***
486 
487 `Poly` will store "small" objects in an internal buffer, avoiding the cost of
488 of dynamic allocations. At present, this size is not configurable; it is
489 pegged at the size of two `double`s.
490 
491 `Poly` objects are always nothrow movable. If you store an object in one that
492 has a potentially throwing move constructor, the object will be stored on the
493 heap, even if it could fit in the internal storage of the `Poly` object.
494 (So be sure to give your objects nothrow move constructors!)
495 
496 `Poly` implements type-erasure in a manner very similar to how the compiler
497 accomplishes virtual dispatch. Every `Poly` object contains a pointer to a
498 table of function pointers. Member function calls involve a double-
499 indirection: once through the v-pointer, and other indirect function call
500 through the function pointer.