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