1# Crash Course: poly
2
3<!--
4@cond TURN_OFF_DOXYGEN
5-->
6# Table of Contents
7
8* [Introduction](#introduction)
9  * [Other libraries](#other-libraries)
10* [Concept and implementation](#concept-and-implementation)
11  * [Deduced interface](#deduced-interface)
12  * [Defined interface](#defined-interface)
13  * [Fullfill a concept](#fullfill-a-concept)
14* [Inheritance](#inheritance)
15* [Static polymorphism in the wild](#static-polymorphism-in-the-wild)
16* [Storage size and alignment requirement](#storage-size-and-alignment-requirement)
17<!--
18@endcond TURN_OFF_DOXYGEN
19-->
20
21# Introduction
22
23Static polymorphism is a very powerful tool in C++, albeit sometimes cumbersome
24to obtain.<br/>
25This module aims to make it simple and easy to use.
26
27The library allows to define _concepts_ as interfaces to fullfill with concrete
28classes withouth having to inherit from a common base.<br/>
29This is, among others, one of the advantages of static polymorphism in general
30and of a generic wrapper like that offered by the `poly` class template in
31particular.<br/>
32What users get is an object that can be passed around as such and not through a
33reference or a pointer, as happens when it comes to working with dynamic
34polymorphism.
35
36Since the `poly` class template makes use of `entt::any` internally, it also
37supports most of its feature. Among the most important, the possibility to
38create aliases to existing and thus unmanaged objects. This allows users to
39exploit the static polymorphism while maintaining ownership of objects.<br/>
40Likewise, the `poly` class template also benefits from the small buffer
41optimization offered by the `entt::any` class and therefore minimizes the number
42of allocations, avoiding them altogether where possible.
43
44## Other libraries
45
46There are some very interesting libraries regarding static polymorphism.<br/>
47Among all, the two that I prefer are:
48
49* [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right.
50* [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md):
51  a class template that makes it easy to define a type-erasing polymorphic
52  object wrapper.
53
54The former is admittedly an experimental library, with many interesting ideas.
55I've some doubts about the usefulness of some feature in real world projects,
56but perhaps my lack of experience comes into play here. In my opinion, its only
57flaw is the API which I find slightly more cumbersome than other solutions.<br/>
58The latter was undoubtedly a source of inspiration for this module, although I
59opted for different choices in the implementation of both the final API and some
60feature.
61
62Either way, the authors are gurus of the C++ community, people I only have to
63learn from.
64
65# Concept and implementation
66
67The first thing to do to create a _type-erasing polymorphic object wrapper_ (to
68use the terminology introduced by Eric Niebler) is to define a _concept_ that
69types will have to adhere to.<br/>
70For this purpose, the library offers a single class that supports both deduced
71and fully defined interfaces. Although having interfaces deduced automatically
72is convenient and allows users to write less code in most cases, this has some
73limitations and it's therefore useful to be able to get around the deduction by
74providing a custom definition for the static virtual table.
75
76Once the interface is defined, it will be sufficient to provide a generic
77implementation to fullfill the concept.<br/>
78Also in this case, the library allows customizations based on types or families
79of types, so as to be able to go beyond the generic case where necessary.
80
81## Deduced interface
82
83This is how a concept with a deduced interface is introduced:
84
85```cpp
86struct Drawable: entt::type_list<> {
87    template<typename Base>
88    struct type: Base {
89        void draw() { this->template invoke<0>(*this); }
90    };
91
92    // ...
93};
94```
95
96It's recognizable by the fact that it inherits from an empty type list.<br/>
97Functions can also be const, accept any number of paramters and return a type
98other than `void`:
99
100```cpp
101struct Drawable: entt::type_list<> {
102    template<typename Base>
103    struct type: Base {
104        bool draw(int pt) const { return this->template invoke<0>(*this, pt); }
105    };
106
107    // ...
108};
109```
110
111In this case, all parameters must be passed to `invoke` after the reference to
112`this` and the return value is whatever the internal call returns.<br/>
113As for `invoke`, this is a name that is injected into the _concept_ through
114`Base`, from which one must necessarily inherit. Since it's also a dependent
115name, the `this-> template` form is unfortunately necessary due to the rules of
116the language. However, there exists also an alternative that goes through an
117external call:
118
119```cpp
120struct Drawable: entt::type_list<> {
121    template<typename Base>
122    struct type: Base {
123        bool draw() const { entt::poly_call<0>(*this); }
124    };
125
126    // ...
127};
128```
129
130Once the _concept_ is defined, users must provide a generic implementation of it
131in order to tell the system how any type can satisfy its requirements. This is
132done via an alias template within the concept itself.<br/>
133The index passed as a template parameter to either `invoke` or `poly_call`
134refers to how this alias is defined.
135
136## Defined interface
137
138A fully defined concept is no different to one for which the interface is
139deduced, with the only difference that the list of types is not empty this time:
140
141```cpp
142struct Drawable: entt::type_list<void()> {
143    template<typename Base>
144    struct type: Base {
145        void draw() { entt::poly_call<0>(*this); }
146    };
147
148    // ...
149};
150```
151
152Again, parameters and return values other than `void` are allowed. Also, the
153function type must be const when the method to bind to it is const:
154
155```cpp
156struct Drawable: entt::type_list<bool(int) const> {
157    template<typename Base>
158    struct type: Base {
159        bool draw(int pt) const { return entt::poly_call<0>(*this, pt); }
160    };
161
162    // ...
163};
164```
165
166Why should a user fully define a concept if the function types are the same as
167the deduced ones?<br>
168Because, in fact, this is exactly the limitation that can be worked around by
169manually defining the static virtual table.
170
171When things are deduced, there is an implicit constraint.<br/>
172If the concept exposes a member function called `draw` with function type
173`void()`, a concept can be satisfied:
174
175* Either by a class that exposes a member function with the same name and the
176  same signature.
177
178* Or through a lambda that makes use of existing member functions from the
179  interface itself.
180
181In other words, it's not possible to make use of functions not belonging to the
182interface, even if they are present in the types that fulfill the concept.<br/>
183Similarly, it's not possible to deduce a function in the static virtual table
184with a function type different from that of the associated member function in
185the interface itself.
186
187Explicitly defining a static virtual table suppresses the deduction step and
188allows maximum flexibility when providing the implementation for a concept.
189
190## Fullfill a concept
191
192The `impl` alias template of a concept is used to define how it's fulfilled:
193
194```cpp
195struct Drawable: entt::type_list<> {
196    // ...
197
198    template<typename Type>
199    using impl = entt::value_list<&Type::draw>;
200};
201```
202
203In this case, it's stated that the `draw` method of a generic type will be
204enough to satisfy the requirements of the `Drawable` concept.<br/>
205Both member functions and free functions are supported to fullfill concepts:
206
207```cpp
208template<typename Type>
209void print(Type &self) { self.print(); }
210
211struct Drawable: entt::type_list<void()> {
212    // ...
213
214    template<typename Type>
215    using impl = entt::value_list<&print<Type>>;
216};
217```
218
219Likewise, as long as the parameter types and return type support conversions to
220and from those of the function type referenced in the static virtual table, the
221actual implementation may differ in its function type since it's erased
222internally.<br/>
223Moreover, the `self` parameter isn't strictly required by the system and can be
224left out for free functions if not required.
225
226Refer to the inline documentation for more details.
227
228# Inheritance
229
230_Concept inheritance_ is straightforward due to how poly looks like in `EnTT`.
231Therefore, it's quite easy to build hierarchies of concepts if necessary.<br/>
232The only constraint is that all concepts in a hierarchy must belong to the same
233_family_, that is, they must be either all deduced or all defined.
234
235For a deduced concept, inheritance is achieved in a few steps:
236
237```cpp
238struct DrawableAndErasable: entt::type_list<> {
239    template<typename Base>
240    struct type: typename Drawable::template type<Base> {
241        static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<Drawable>::type>;
242        void erase() { entt::poly_call<base + 0>(*this); }
243    };
244
245    template<typename Type>
246    using impl = entt::value_list_cat_t<
247        typename Drawable::impl<Type>,
248        entt::value_list<&Type::erase>
249    >;
250};
251```
252
253The static virtual table is empty and must remain so.<br/>
254On the other hand, `type` no longer inherits from `Base` and instead forwards
255its template parameter to the type exposed by the _base class_. Internally, the
256size of the static virtual table of the base class is used as an offset for the
257local indexes.<br/>
258Finally, by means of the `value_list_cat_t` utility, the implementation consists
259in appending the new functions to the previous list.
260
261As for a defined concept instead, also the list of types must be extended, in a
262similar way to what is shown for the implementation of the above concept.<br/>
263To do this, it's useful to declare a function that allows to convert a _concept_
264into its underlying `type_list` object:
265
266```cpp
267template<typename... Type>
268entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);
269```
270
271The definition isn't strictly required, since the function will only be used
272through a `decltype` as it follows:
273
274```cpp
275struct DrawableAndErasable: entt::type_list_cat_t<
276    decltype(as_type_list(std::declval<Drawable>())),
277    entt::type_list<void()>
278> {
279    // ...
280};
281```
282
283Similar to above, `type_list_cat_t` is used to concatenate the underlying static
284virtual table with the new function types.<br/>
285Everything else is the same as already shown instead.
286
287# Static polymorphism in the wild
288
289Once the _concept_ and implementation have been introduced, it will be possible
290to use the `poly` class template to contain instances that meet the
291requirements:
292
293```cpp
294using drawable = entt::poly<Drawable>;
295
296struct circle {
297    void draw() { /* ... */ }
298};
299
300struct square {
301    void draw() { /* ... */ }
302};
303
304// ...
305
306drawable instance{circle{}};
307instance->draw();
308
309instance = square{};
310instance->draw();
311```
312
313The `poly` class template offers a wide range of constructors, from the default
314one (which will return an uninitialized `poly` object) to the copy and move
315constructors, as well as the ability to create objects in-place.<br/>
316Among others, there is also a constructor that allows users to wrap unmanaged
317objects in a `poly` instance (either const or non-const ones):
318
319```cpp
320circle shape;
321drawable instance{std::in_place_type<circle &>, shape};
322```
323
324Similarly, it's possible to create non-owning copies of `poly` from an existing
325object:
326
327```cpp
328drawable other = instance.as_ref();
329```
330
331In both cases, although the interface of the `poly` object doesn't change, it
332won't construct any element or take care of destroying the referenced objects.
333
334Note also how the underlying concept is accessed via a call to `operator->` and
335not directly as `instance.draw()`.<br/>
336This allows users to decouple the API of the wrapper from that of the concept.
337Therefore, where `instance.data()` will invoke the `data` member function of the
338poly object, `instance->data()` will map directly to the functionality exposed
339by the underlying concept.
340
341# Storage size and alignment requirement
342
343Under the hood, the `poly` class template makes use of `entt::any`. Therefore,
344it can take advantage of the possibility of defining at compile-time the size of
345the storage suitable for the small buffer optimization as well as the alignment
346requirements:
347
348```cpp
349entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>
350```
351
352The default size is `sizeof(double[2])`, which seems like a good compromise
353between a buffer that is too large and one unable to hold anything larger than
354an integer. The alignment requirement is optional instead and by default such
355that it's the most stringent (the largest) for any object whose size is at most
356equal to the one provided.<br/>
357It's worth noting that providing a size of 0 (which is an accepted value in all
358respects) will force the system to dynamically allocate the contained objects in
359all cases.
360