├── README.md └── include └── polytail.hpp /README.md: -------------------------------------------------------------------------------- 1 | polytail 2 | ======== 3 | Rust-like trait-based polymorphism for C++ 4 | 5 | ## Overview 6 | One thing that Rust does better than C++ is its trait system. This library aims 7 | to offer some important benefits of the trait system: 8 | 9 | * Non-intrusive 10 | * Extensible (not restricted to inheritance hierarchy) 11 | * Semantic-based 12 | 13 | It can be used with or without type-erasure. 14 | 15 | ### Requirements 16 | - C++20 17 | 18 | ### Header 19 | This is a header-only library with a single header and without extra dependancy. 20 | ```c++ 21 | #include 22 | ``` 23 | ### Synopsis 24 | ```c++ 25 | namespace pltl 26 | { 27 | template 28 | struct impl_for; // User-supplied specialization. 29 | 30 | template 31 | inline Trait const vtable; // User-supplied specialization. 32 | 33 | template 34 | struct composite; 35 | 36 | template 37 | struct boxed; 38 | 39 | template 40 | struct dyn_ptr; 41 | 42 | template 43 | struct dyn_ref; 44 | 45 | struct mut_this; 46 | struct const_this; 47 | 48 | template 49 | inline std::unique_ptr, box_deleter> box_unique(T val); 50 | 51 | template 52 | inline std::shared_ptr> box_shared(T val); 53 | } 54 | ``` 55 | 56 | ## How to 57 | ### Define a trait: 58 | ```c++ 59 | namespace StrConv 60 | { 61 | struct trait 62 | { 63 | std::string(*to_str)(pltl::const_this self); 64 | void(*from_str)(pltl::mut_this self, std::string_view str); 65 | }; 66 | 67 | template 68 | inline auto to_str(Self&& self) -> POLYTAIL_RET(std::string, to_str(self)) 69 | 70 | template 71 | inline auto from_str(Self&& self, std::string_view str) -> POLYTAIL_RET(void, from_str(self, str)) 72 | } 73 | 74 | template 75 | inline StrConv::trait const pltl::vtable 76 | { 77 | delegate::to_str>, 78 | delegate::from_str> 79 | }; 80 | ``` 81 | 82 | ### Implement a trait for a type: 83 | ```c++ 84 | template<> 85 | struct pltl::impl_for 86 | { 87 | static std::string to_str(int self) 88 | { 89 | return std::to_string(self); 90 | } 91 | 92 | static void from_str(int& self, std::string_view str) 93 | { 94 | std::from_chars(str.data(), str.data() + str.size(), self); 95 | } 96 | }; 97 | ``` 98 | 99 | ### Use a trait 100 | ```c++ 101 | std::string s; 102 | int i = 42; 103 | // Without type-erasure. 104 | s = StrConv::to_str(i); 105 | assert(s == "42"); 106 | StrConv::from_str(i, "25"); 107 | assert(i == 25); 108 | 109 | // With type-erasure & ADL. 110 | pltl::dyn_ref erased(i); 111 | s = to_str(erased); 112 | assert(s == "25"); 113 | from_str(erased, "1"); 114 | assert(a == 1); 115 | ``` 116 | 117 | ### Compose traits on demand: 118 | ```c++ 119 | using Trait = pltl::composite; 120 | int i = 42; 121 | pltl::dyn_ref erased(i); 122 | print(erased); // Print 123 | from_str(erased, "25"); // StrConv 124 | pltl::dyn_ref sub(erased); // Can degrade to sub-trait. 125 | print(sub); 126 | ``` 127 | 128 | ### Create boxed values: 129 | ```c++ 130 | auto p = pltl::box_unique(42); // Or box_shared. 131 | boxed& ref = *p; 132 | pltl::dyn_ref sub(ref); // Can degrade to sub-trait. 133 | assert(to_str(ref) == to_str(sub)); 134 | ``` 135 | 136 | ## License 137 | 138 | Copyright (c) 2019-2022 Jamboree 139 | 140 | Distributed under the Boost Software License, Version 1.0. (See accompanying 141 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -------------------------------------------------------------------------------- /include/polytail.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2019-2022 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef POLYTAIL_HPP_INCLUDED 8 | #define POLYTAIL_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | 13 | namespace pltl { 14 | template 15 | struct enable_expr { 16 | using type = T; 17 | }; 18 | 19 | template 20 | using enable_expr_t = typename enable_expr::type; 21 | 22 | template 23 | concept PtrComparable = requires(T* a, U* b) { 24 | a <=> b; 25 | }; 26 | 27 | template 28 | struct impl_for; 29 | 30 | template 31 | concept ImplFor = requires { 32 | impl_for{}; 33 | }; 34 | 35 | template 36 | requires ImplFor 37 | constexpr impl_for get_impl(T const&) { return {}; } 38 | 39 | template 40 | inline Trait const vtable = nullptr; 41 | 42 | template 43 | struct composite; 44 | 45 | template 46 | concept AllImplemented = (... && ImplFor); 47 | 48 | namespace detail { 49 | template 50 | struct is_composite : std::false_type {}; 51 | 52 | template 53 | struct is_composite> : std::true_type {}; 54 | 55 | template 56 | constexpr bool is_composite_v = is_composite::value; 57 | 58 | template 59 | struct trait_object { 60 | constexpr trait_object() : _vptr() {} 61 | 62 | constexpr explicit trait_object(Trait const* p) : _vptr(p) {} 63 | 64 | template 65 | constexpr explicit trait_object(composite const* p) 66 | : _vptr(&static_cast(*p)) {} 67 | 68 | template 69 | constexpr explicit trait_object(composite const& p) 70 | : _vptr(&static_cast(p)) {} 71 | 72 | private: 73 | friend Trait const* get_vdata(trait_object const* p) { 74 | return p->_vptr; 75 | } 76 | friend Trait const& get_vtable(trait_object const* p) { 77 | return *p->_vptr; 78 | } 79 | 80 | Trait const* _vptr; 81 | }; 82 | 83 | template 84 | struct trait_object { 85 | constexpr trait_object() : _vtable() {} 86 | 87 | template 88 | constexpr explicit trait_object(Trait2 const* p) : _vtable(*p) {} 89 | 90 | template 91 | constexpr explicit trait_object(composite const& p) 92 | : _vtable(p) {} 93 | 94 | private: 95 | friend Trait const& get_vdata(trait_object const* p) { 96 | return p->_vtable; 97 | } 98 | friend Trait const& get_vtable(trait_object const* p) { 99 | return p->_vtable; 100 | } 101 | 102 | Trait _vtable; 103 | }; 104 | 105 | template 106 | using trait_base = 107 | trait_object; 108 | 109 | template 110 | struct indirect_trait_base : trait_base { 111 | constexpr explicit indirect_trait_base(Trait const& p) 112 | : trait_base(&p) {} 113 | operator Trait const &() const noexcept { return get_vtable(this); } 114 | }; 115 | 116 | template 117 | using proxy_trait_base = trait_base>; 118 | 119 | template 120 | struct indirect_data { 121 | std::uintptr_t data; 122 | }; 123 | 124 | template<> 125 | struct indirect_data : indirect_data {}; 126 | 127 | template 128 | struct proxy : indirect_data, proxy_trait_base { 129 | protected: 130 | proxy() noexcept : indirect_data{0} {} 131 | 132 | template 133 | proxy(std::uintptr_t data, Vptr vptr) noexcept 134 | : indirect_data{data}, proxy_trait_base{vptr} {} 135 | }; 136 | 137 | template 138 | struct boxed_trait : Trait { 139 | std::uintptr_t (*data)(void const*) noexcept; 140 | void (*destruct)(void*) noexcept; 141 | }; 142 | 143 | template 144 | using deduce_t = decltype(Trait::deduce(std::declval())); 145 | } // namespace detail 146 | 147 | template 148 | struct composite : detail::indirect_trait_base... { 149 | constexpr composite(Trait const&... vtable) 150 | : detail::indirect_trait_base{vtable}... {} 151 | 152 | template 153 | constexpr composite(composite const& other) noexcept 154 | : detail::indirect_trait_base(other)... {} 155 | }; 156 | 157 | template 158 | inline composite const vtable, T>{ 159 | vtable...}; 160 | 161 | template T> 162 | struct impl_for, T> {}; 163 | 164 | namespace detail { 165 | template 166 | struct match_trait_no_const : std::is_convertible { 167 | }; 168 | 169 | template 170 | struct match_trait_no_const, U> 171 | : std::conjunction...> {}; 172 | 173 | template << 1 | std::is_const_v> 175 | struct match_trait : match_trait_no_const {}; 176 | 177 | template 178 | struct match_trait : std::false_type {}; 179 | 180 | template 181 | constexpr bool match_trait_v = match_trait::value; 182 | } // namespace detail 183 | 184 | template 185 | requires detail::match_trait_v 186 | inline Trait const& get_impl(detail::trait_object const& p) { 187 | return get_vtable(&p); 188 | } 189 | 190 | template 191 | inline detail::deduce_t const& 192 | get_impl(detail::trait_object const& p) { 193 | return get_vtable(&p); 194 | } 195 | 196 | template 197 | using impl_t = std::decay_t(std::declval()))>; 198 | 199 | template 200 | struct boxed : detail::trait_object, false> { 201 | explicit boxed(detail::boxed_trait const* vptr) 202 | : detail::trait_object, false>{vptr} {} 203 | 204 | std::uintptr_t data() const noexcept { 205 | return get_vtable(this).data(this); 206 | } 207 | friend void destruct(boxed* self) noexcept { 208 | get_vtable(self).destruct(self); 209 | } 210 | }; 211 | 212 | template 213 | struct dyn_ptr : detail::proxy { 214 | using base_t = detail::proxy; 215 | 216 | dyn_ptr() = default; 217 | 218 | dyn_ptr(std::nullptr_t) noexcept {} 219 | 220 | template 221 | requires ImplFor dyn_ptr(T* p) 222 | noexcept 223 | : base_t(reinterpret_cast(p), &vtable) {} 224 | 225 | template 226 | requires detail::match_trait_v dyn_ptr(dyn_ptr p) 227 | noexcept : base_t(p.data, get_vdata(&p)) {} 228 | 229 | template 230 | requires detail::match_trait_v dyn_ptr(boxed* p) 231 | noexcept : base_t(p->data(), get_vdata(p)) {} 232 | 233 | explicit dyn_ptr(base_t base) : base_t(base) {} 234 | 235 | explicit operator bool() const noexcept { return !!this->data; } 236 | }; 237 | 238 | template 239 | struct dyn_ptr : detail::proxy { 240 | using base_t = detail::proxy; 241 | 242 | dyn_ptr() = default; 243 | 244 | dyn_ptr(std::nullptr_t) noexcept {} 245 | 246 | template 247 | requires ImplFor dyn_ptr(T const* p) 248 | noexcept 249 | : base_t(reinterpret_cast(p), &vtable) {} 250 | 251 | template 252 | requires detail::match_trait_v 253 | dyn_ptr(dyn_ptr p) 254 | noexcept : base_t(p.data, get_vdata(&p)) {} 255 | 256 | template 257 | requires detail::match_trait_v 258 | dyn_ptr(boxed const* p) 259 | noexcept : base_t(p->data(), get_vdata(p)) {} 260 | 261 | explicit dyn_ptr(base_t base) : base_t(base) {} 262 | 263 | explicit operator bool() const noexcept { return !!this->data; } 264 | }; 265 | 266 | template 267 | requires PtrComparable 268 | inline auto operator<=>(dyn_ptr a, dyn_ptr b) noexcept { 269 | return a.data <=> b.data; 270 | } 271 | 272 | template 273 | requires PtrComparable 274 | inline auto operator==(dyn_ptr a, dyn_ptr b) noexcept { 275 | return a.data == b.data; 276 | } 277 | 278 | template 279 | inline bool operator==(dyn_ptr a, std::nullptr_t) noexcept { 280 | return !a.data; 281 | } 282 | 283 | template 284 | struct dyn_ref : detail::proxy { 285 | using base_t = detail::proxy; 286 | 287 | template 288 | requires ImplFor dyn_ref(T& r) 289 | noexcept 290 | : base_t(reinterpret_cast(&r), &vtable) {} 291 | 292 | template 293 | requires detail::match_trait_v dyn_ref(dyn_ref r) 294 | noexcept : base_t(r.data, get_vdata(&r)) {} 295 | 296 | template 297 | requires detail::match_trait_v dyn_ref(boxed& r) 298 | noexcept : base_t(r.data(), get_vdata(&r)) {} 299 | 300 | dyn_ptr get_ptr() const noexcept { 301 | return dyn_ptr(*this); 302 | } 303 | }; 304 | 305 | template 306 | struct dyn_ref : detail::proxy { 307 | using base_t = detail::proxy; 308 | 309 | template 310 | requires ImplFor dyn_ref(T const& r) 311 | noexcept 312 | : base_t(reinterpret_cast(&r), &vtable) {} 313 | 314 | template 315 | requires detail::match_trait_v 316 | dyn_ref(dyn_ref r) 317 | noexcept : base_t(r.data, get_vdata(&r)) {} 318 | 319 | template 320 | requires detail::match_trait_v 321 | dyn_ref(boxed const& r) 322 | noexcept : base_t(r.data(), get_vdata(&r)) {} 323 | 324 | dyn_ptr get_ptr() const noexcept { 325 | return dyn_ptr(*this); 326 | } 327 | }; 328 | 329 | namespace detail { 330 | template 331 | struct boxer : boxed { 332 | explicit boxer(T&& val) 333 | : boxed(&boxed_vtable), _data(std::move(val)) {} 334 | 335 | private: 336 | static std::uintptr_t data_impl(void const* self) noexcept { 337 | return reinterpret_cast( 338 | &static_cast(self)->_data); 339 | } 340 | static void destruct_impl(void* self) noexcept { 341 | static_cast(self)->~boxer(); 342 | } 343 | static inline boxed_trait boxed_vtable{ 344 | vtable, data_impl, destruct_impl}; 345 | 346 | T _data; 347 | }; 348 | } // namespace detail 349 | 350 | struct box_deleter { 351 | template 352 | void operator()(boxed* p) const noexcept { 353 | destruct(p); 354 | ::operator delete(p); 355 | } 356 | }; 357 | 358 | template 359 | inline std::unique_ptr, box_deleter> box_unique(T val) { 360 | return std::unique_ptr, box_deleter>( 361 | new detail::boxer(std::move(val))); 362 | } 363 | 364 | template 365 | inline std::shared_ptr> box_shared(T val) { 366 | return std::make_shared>(std::move(val)); 367 | } 368 | 369 | struct mut_this { 370 | std::uintptr_t self; 371 | 372 | mut_this(detail::indirect_data h) noexcept : self(h.data) {} 373 | 374 | template 375 | mut_this(boxed& b) noexcept : self(b.data()) {} 376 | 377 | template 378 | T& get() noexcept { 379 | return *reinterpret_cast(self); 380 | } 381 | }; 382 | 383 | struct const_this { 384 | std::uintptr_t self; 385 | 386 | const_this(detail::indirect_data h) noexcept : self(h.data) {} 387 | 388 | template 389 | const_this(boxed const& b) noexcept : self(b.data()) {} 390 | 391 | template 392 | T const& get() noexcept { 393 | return *reinterpret_cast(self); 394 | } 395 | }; 396 | 397 | struct ignore_this { 398 | ignore_this() = default; 399 | template 400 | constexpr ignore_this(T const& b) {} 401 | }; 402 | 403 | namespace detail { 404 | template 405 | using fn_ptr = Sig*; 406 | 407 | template 408 | struct no_this : std::false_type {}; 409 | 410 | template 411 | struct no_this : std::true_type {}; 412 | 413 | // MSVC cannot deduce noexcept(B), so another a specialization. 414 | template 415 | struct no_this : std::true_type {}; 416 | 417 | template 418 | struct delegate_no_this { 419 | template 420 | constexpr operator fn_ptr() const { 421 | return 422 | [](U, A... a) -> R { return F({}, std::forward(a)...); }; 423 | } 424 | }; 425 | 426 | template 427 | struct delegate_t { 428 | template 429 | constexpr operator fn_ptr() const { 430 | return [](U self, A... a) -> R { 431 | return F(self.template get(), std::forward(a)...); 432 | }; 433 | } 434 | }; 435 | 436 | template 437 | requires(no_this::value) struct delegate_t 438 | : delegate_no_this { 439 | }; 440 | } // namespace detail 441 | 442 | template 443 | constexpr typename detail::delegate_t delegate{}; 444 | } // namespace pltl 445 | 446 | #define Zz_POLYTAIL_RM_PAREN(...) __VA_ARGS__ 447 | #define Zz_POLYTAIL_RET(T, expr) \ 448 | ::pltl::enable_expr_t { return expr; } 449 | #define Zz_POLYTAIL_RET_PAREN(T, expr) \ 450 | ::pltl::enable_expr_t { \ 451 | return expr; \ 452 | } 453 | #define POLYTAIL_RET(T, expr) \ 454 | Zz_POLYTAIL_RET(T, ::pltl::get_impl(self).expr) 455 | #define POLYTAIL_RET_PAREN(T, expr) \ 456 | Zz_POLYTAIL_RET_PAREN(T, ::pltl::get_impl(self).expr) 457 | #define POLYTAIL_RET_TMP(T, trait, expr) \ 458 | Zz_POLYTAIL_RET(T, ::pltl::get_impl(self).expr) 459 | #define POLYTAIL_RET_TMP_PAREN(T, trait, expr) \ 460 | Zz_POLYTAIL_RET_PAREN(T, ::pltl::get_impl(self).expr) 461 | 462 | #endif --------------------------------------------------------------------------------