// g++ -std=c++14 -Wall -Wextra -Wpedantic -o seany seany.cpp // Concept-based Relaying Any type // C++14 Sean Parent-inspired concept-parameterized std::any-like type. // https://git.rcrnstn.net/rcrnstn/crany // CXXFLAGS='' // "concept-model", "runtime concept", "virtual concept" idiom. // https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#t5-combine-generic-and-oo-techniques-to-amplify-their-strengths-not-their-costs // https://wiki.c2.com/?ExternalPolymorphism // https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-EP.pdf // https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Polymorphic_Value_Types // Patterns: // - Adapter pattern // - [Wikipedia](https://en.wikipedia.org/wiki/Adapter_pattern) // - [Gamma, E. et al. *Design Patterns: Elements of Reusable Object-Oriented Software*, Addison–Wesley, 1995](https://en.wikipedia.org/wiki/Design_Patterns) // - Decorator pattern // - [Wikipedia](https://en.wikipedia.org/wiki/Decorator_pattern) // - [Gamma, E. et al. *Design Patterns: Elements of Reusable Object-Oriented Software*, Addison–Wesley, 1995](https://en.wikipedia.org/wiki/Design_Patterns) // - External polymorphism pattern // - [WikiWikiWeb](https://wiki.c2.com/?ExternalPolymorphism) // - [Cleeland, C., D. C. Schmidt, and T. Harrison. "External Polymorphism"](https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-EP.pdf) // Kevlin Henney // [Valued Conversions](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.374.2252&rep=rep1&type=pdf) // C++ Report 12(7) 2000 // [Sean Parent](https://sean-parent.stlab.cc) // [Better Code: Runtime Polymorphism](https://sean-parent.stlab.cc/papers-and-presentations/#better-code-runtime-polymorphism) // NDC 2017, [video](https://www.youtube.com/watch?v=QGcVXgEVMJg) // [Inheritance Is The Base Class of Evil](https://sean-parent.stlab.cc/papers-and-presentations/#inheritance-is-the-base-class-of-evil) // GoingNative 2013, [video](https://www.youtube.com/watch?v=bIhUE5uUFOA) // [Value Semantics and Concept-based Polymorphism](https://sean-parent.stlab.cc/papers-and-presentations/#value-semantics-and-concept-based-polymorphism) // CppNow 2012, [video](https://www.youtube.com/watch?v=_BpMYeUFXv8) // [Concept-Based Runtime Polymorphism](https://stlab.cc/legacy/figures/Boost_poly.pdf) // BoostCon 2007 // [Runtime Concepts](https://stlab.cc/legacy/runtime-concepts.html) // Louis Dionne // Runtime Polymorphism: Back to the Basics // [C++Now 2018](https://ldionne.github.io/cppnow-2018-runtime-polymorphism), [video](https://www.youtube.com/watch?v=OtU51Ytfe04) // [CppCon 2017](https://ldionne.github.io/cppcon-2017-runtime-polymorphism), [video](https://www.youtube.com/watch?v=gVGtNFg4ay0) // Zach Laine // Pragmatic Type Erasure: Solving OOP Problems w/ Elegant Design Pattern // CppCon 2014, [video](https://www.youtube.com/watch?v=0I0FD3N5cgM) // Klaus Igelberger // Breaking Dependencies: Type Erasure - A Design Analysis // CppCon 2021, [video](https://www.youtube.com/watch?v=4eeESJQk-mw&t=23m26s) // Sy Brand // Dynamic Polymorphism with Metaclasses and Code Injection // CppCon 2020, [video](https://www.youtube.com/watch?v=8c6BAQcYF_E) // Related projects: // - Adbobe's [Poly](https://stlab.adobe.com/group__poly__related.html) // - Facebook's [Folly Poly](https://github.com/facebook/folly/blob/main/folly/docs/Poly.md) // - Louis Dionne's [Dyno](https://github.com/ldionne/dyno) // - https://www.youtube.com/watch?v=OtU51Ytfe04 // - https://www.youtube.com/watch?v=H8lFldGvt9w // - https://www.youtube.com/watch?v=gVGtNFg4ay0 // Improvements: // - Read the code for `std::any`/`std::bad_any_cast` and steal away :) // - Make the code slightly uglier to support C++11? Actually, can we go even // lower? When was template template arguments introduced? // - Optimization. Ok, we got everything we want in the proof of concept, look // into Small Buffer Optimization? Something like `dyno`'s storage policy? // Luckily, `dyno`'s author recommends against futzing with the vtable, so // implementing SBO might not be too hard. See also // <https://www.youtube.com/watch?v=va9I2qivBOA&t=40m21s>. // - Both `dyno` and `folly::Poly` uses roughly the same example. Use it too. // - Introduce another template parameter to `Concept` so that it is possible // to use a template specialization as an Adapter? // - Check if it's possible to supply only `operator->()` in the `Concept` and // *all* member functions forwarded, at the cost of using `->` instead of // `.`. // - It would be nice if we could handle (non-owning) pointers as well. Do we? // - Diagrams with https://plantuml.com. // SOLVED: // - SOLVED(Using `std::is_void` seems reasonable) `CranySelf` is leaked into // the global namespace. Encapsulation. The technique of putting it in a // template specialization of `Crany` so it can be inherited by the general // `Crany` is harder when the template argument itself is a template (What // should be the specialization? The usual `void` doesn't work. // `std::type_identity` was not introduced until C++20.). // - SOLVED(we can just provide a templated free function that forwards to its // argument and template speclialize that on `Shapes`) Sean Parent's // implementation delegates to free functions in the `Model` but we do not. // Doing so would allow us to implement e.g. `area(Shapes const &)` without // having to do anything more complicated than `using Shapes = // std::vector<Shape>`, which would be nice. Maybe make `Crany::Model` // `public` and allow the client concept to specialize on it? // - SOLVED, AMAZING :D Do away with the user having to specify the // `BaseConcept` twice, once generally and once as a `void` specialization. // This would cut user boilerplate down by a factor of 2, but seems very hard // to do. Maybe possible with some macro magic? // Document: // - Document requirements of wrapped types. Is `CopyConstructible` enough? // - Support e.g. `Square` member functions returning `Square &`. Actually, // this is not possible (Square should return `Square &`, but there is no // common base class that `Crany` can return. Document this fact. // - Document (test first) what a user has to do if they want their `Concept` // to include a member function called `self`. Actually, this is not possible // without making `self` a `static` member and take the this pointer as an // argument. This would make the API uglier, just don't support it. Document // this fact. // - If a user forgets to mark a member function `virtual` in the `Concept`, // the program segfaults :S Would be nice to catch that at compile time. It's // probably caused by infinite recursion in `Concept<ConceptSelf>` (verify!). // We probably can't do much about it, but at least document. // TODO: Compare compiler output (as performance indicator) for both this // parameterized version and a version that hardcodes the member functions. #include <memory> #include <type_traits> template<template<typename> class = std::is_void> class Crany; /// CranySelf template<> class Crany<> { template<template<typename> class> friend class Crany; template<typename T> struct CranySelf { protected: auto & self() { return *((T *)this)->self_; } auto const & self() const { return *((T const *)this)->self_; } }; }; /// Crany template<template<typename> class BaseConcept> class Crany final : public BaseConcept<Crany<>::CranySelf<Crany<BaseConcept>>> { public: /// Special member functions template<typename Self> Crany(Self self) : self_{new Model<Self>(std::move(self))} {} Crany(Crany const & other) : self_(other.self_->clone()) {} Crany & operator=(Crany const & other) { return *this = Crany(other); } Crany(Crany && other) = default; Crany & operator=(Crany && other) = default; private: /// CranySelf friend Crany<>::CranySelf<Crany<BaseConcept>>; /// ConceptSelf struct ConceptSelf { protected: auto & self() { return *(BaseConcept<ConceptSelf> *)this; } auto const & self() const { return *(BaseConcept<ConceptSelf> const *)this; } }; /// Concept struct Concept : BaseConcept<ConceptSelf> { virtual ~Concept() = default; virtual Concept * clone() const = 0; }; /// ModelSelf template<typename T> struct ModelSelf : Concept { protected: auto & self() { return ((T *)this)->self_; } auto const & self() const { return ((T const *)this)->self_; } }; /// Model template<typename Self> struct Model final : BaseConcept<ModelSelf<Model<Self>>> { Model(Self self) : self_{std::move(self)} {} Concept * clone() const { return new Model<Self>(*this); } Self self_; }; /// Self std::unique_ptr<Concept> self_; // crany_cast template<typename T, typename Crany> friend T const * crany_cast(Crany const * crany) noexcept; template<typename T, typename Crany> friend T * crany_cast(Crany * crany) noexcept; template<typename T, typename Crany> friend T crany_cast(Crany const & crany); template<typename T, typename Crany> friend T crany_cast(Crany & crany); template<typename T, typename Crany> friend T crany_cast(Crany && crany); }; /// bad_crany_cast class bad_crany_cast : public std::bad_cast { template<template<typename> class> friend class Crany; }; /// crany_cast template<typename T, typename Crany> T const * crany_cast(Crany const * crany) noexcept { if (!crany) return nullptr; if(auto * model = dynamic_cast<typename Crany::Model<T> *>(crany->self_.get())) return &model->self_; return nullptr; } template<typename T, typename Crany> T * crany_cast(Crany * crany) noexcept { return const_cast<T *>(*const_cast<T const *>(crany)); } template<typename T, typename Crany> T crany_cast(Crany const & crany) { using U = std::remove_cv_t<std::remove_reference_t<T>>; if (auto p = crany_cast<U>(&crany)) return static_cast<T>(*p); throw bad_crany_cast(); // TODO } template<typename T, typename Crany> T crany_cast(Crany & crany) { using U = std::remove_cv_t<std::remove_reference_t<T>>; if (auto p = crany_cast<U>(&crany)) return static_cast<T>(*p); throw bad_crany_cast(); // TODO } template<typename T, typename Crany> T crany_cast(Crany && crany) { using U = std::remove_cv_t<std::remove_reference_t<T>>; if (auto p = crany_cast<U>(&crany)) return static_cast<T>(std::move(*p)); throw bad_crany_cast(); // TODO } #include <cmath> #include <iostream> #include <vector> auto constexpr pi = std::acos(-1); template<typename Self> struct ShapeConcept : Self { virtual float area() const { return self().area(); } virtual void scale(float value) { return self().scale(value); } private: using Self::self; }; using Shape = Crany<ShapeConcept>; using Shapes = std::vector<Shape>; struct Square { float side; float area() const { return side*side; } void scale(float scale) { side *= scale; } }; struct Circle { float radius; float area() const { return pi*radius*radius; } void scale(float scale) { radius *= scale; } }; int main() { auto shapes = Shapes{ Square{2}, Circle{3}, }; shapes.push_back(shapes.front()); shapes.back().scale(2); // std::cout << shapes.front().self() << std::endl; std::cout << std::boolalpha; for (auto const & shape : shapes) std::cout << shape.area() << " " << !!crany_cast<Square>(&shape) << "\n"; }