Last active
September 16, 2023 23:16
-
-
Save jemand2001/9ca1665fa72e3d052188124a2a694ae4 to your computer and use it in GitHub Desktop.
tree-like structures between classes through template inheritance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <iostream> | |
| #include <memory> | |
| #include <ranges> | |
| #include <source_location> | |
| #include <vector> | |
| #include "traits.hpp" | |
| class Scene; | |
| class Entity; | |
| class Component; | |
| template <class Me, class Child, class... Children> | |
| class HasChildren<Me, Child, Children...> { | |
| static_assert(unique<Child, Children...>, | |
| "this class can't work if you have multiple identical types " | |
| "of children"); | |
| template <typename C> | |
| using Vec = std::vector<std::shared_ptr<C>>; | |
| std::tuple<Vec<Child>, Vec<Children>...> children; | |
| public: | |
| template <one_of<Child, Children...> C> | |
| void add_child(std::shared_ptr<C> c) { | |
| if constexpr (can_check_parent<Me, C>) { | |
| if (static_cast<Me *>(this)->check_parent(c)) | |
| throw std::runtime_error("loop detected while adding child!"); | |
| } | |
| auto &them = std::get<Vec<C>>(children); | |
| if (std::ranges::find(them, c) == them.end()) | |
| them.push_back(c); | |
| if constexpr (can_set_parent<Child, Me> && | |
| std::derived_from<Me, std::enable_shared_from_this<Me>>) { | |
| c->set_parent(static_cast<Me *>(this)->shared_from_this()); | |
| } | |
| } | |
| template <one_of<Child, Children...> C> | |
| std::shared_ptr<C> _child(size_t x) { | |
| return std::get<Vec<C>>(children).at(x); | |
| } | |
| template <one_of<Child, Children...> C> | |
| std::shared_ptr<C> child(size_t x) { | |
| return _child<C>(x); | |
| } | |
| std::shared_ptr<Child> child(size_t x) { | |
| static_assert(sizeof...(Children) == 0, | |
| "ambiguous call to " | |
| "child()!"); | |
| return _child<Child>(x); | |
| } | |
| template <one_of<Child, Children...> C> | |
| bool has_child(std::shared_ptr<C> c) { | |
| Vec<C> &that = std::get<Vec<C>>(children); | |
| return std::ranges::find(that, c) != that.end(); | |
| } | |
| template <one_of<Child, Children...> C> | |
| void remove_child(std::shared_ptr<C> c) { | |
| children.remove(c); | |
| } | |
| template <one_of<Child, Children...> C> | |
| std::ranges::ref_view<Vec<C>> _children_of() { | |
| return {std::get<Vec<C>>(children)}; | |
| } | |
| }; | |
| template <class Me> | |
| struct HasChildren<Me> { | |
| static_assert( | |
| false, | |
| "why are you inheriting HasChildren without any child types?"); | |
| }; | |
| template <typename Me, typename Parent, typename... Parents> | |
| class HasParent<Me, Parent, Parents...> { | |
| static_assert(unique<Parent, Parents...>, | |
| "this class can't work if you have multiple identical types " | |
| "of parents"); | |
| std::tuple<std::weak_ptr<Parent>, std::weak_ptr<Parents>...> parents; | |
| public: | |
| template <typename P> | |
| bool check_parent(std::shared_ptr<P> potential) { | |
| if constexpr (std::same_as<P, Me>) | |
| if (potential.get() == static_cast<Me *>(this)) | |
| return true; | |
| auto check = [potential]<typename P_>(std::weak_ptr<P_> parent) { | |
| auto parent_ = parent.lock(); | |
| if constexpr (can_check_parent<P_, P>) { | |
| return parent_ && parent_->check_parent(potential); | |
| } | |
| if constexpr (std::same_as<P, P_>) | |
| return parent_ == potential; | |
| return false; | |
| }; | |
| return (check(std::get<std::weak_ptr<Parent>>(parents)) || ... || | |
| check(std::get<std::weak_ptr<Parents>>(parents))); | |
| } | |
| template <one_of<Parent, Parents...> P> | |
| void set_parent(std::shared_ptr<P> p) { | |
| if (p != std::get<std::weak_ptr<P>>(parents).lock()) | |
| std::get<std::weak_ptr<P>>(parents) = p; | |
| } | |
| template <one_of<Parent, Parents...> P> | |
| std::shared_ptr<P> _try_get_parent() { | |
| return std::get<std::weak_ptr<P>>(parents).lock(); | |
| } | |
| template <one_of<Parent, Parents...> P> | |
| std::shared_ptr<P> try_get_parent() { | |
| return _try_get_parent<P>(); | |
| } | |
| std::shared_ptr<Parent> try_get_parent() { | |
| static_assert(sizeof...(Parents) == 0, | |
| "ambiguous call to " | |
| "try_get_parent()!"); | |
| return _try_get_parent<Parent>(); | |
| } | |
| }; | |
| template <class Me> | |
| struct HasParent<Me> { | |
| static_assert(false, | |
| "why are you inheriting HasParent without any parent types?"); | |
| }; | |
| class Scene : public std::enable_shared_from_this<Scene>, | |
| public HasChildren<Scene, Entity> {}; | |
| class Entity : public std::enable_shared_from_this<Entity>, | |
| public HasParent<Entity, Entity, Scene>, | |
| public HasChildren<Entity, Entity, Component> {}; | |
| class Component : public std::enable_shared_from_this<Component>, | |
| public HasParent<Component, Entity> {}; | |
| // class Wrong : public HasParent<Wrong, int, float, int> {}; | |
| int main() { | |
| auto s = std::make_shared<Scene>(); | |
| s->add_child(std::make_shared<Entity>()); | |
| s->child(0)->add_child(std::make_shared<Entity>()); | |
| s->child(0)->add_child(std::make_shared<Component>()); | |
| std::cout << "root: " << s << '\n'; | |
| std::cout << s->child(0) << '\n'; | |
| std::cout << s->child(0)->try_get_parent<Scene>() << '\n'; | |
| std::cout << s->child(0)->child<Entity>(0) << '\n'; | |
| std::cout << s->child(0)->child<Entity>(0)->try_get_parent<Entity>() | |
| << '\n'; | |
| std::cout << s->child(0)->child<Component>(0) << '\n'; | |
| std::cout << s->child(0)->child<Component>(0)->try_get_parent() << '\n'; | |
| std::cout << std::boolalpha << s->has_child(s->child(0)) << '\n'; | |
| auto e1 = std::make_shared<Entity>(); | |
| auto e2 = std::make_shared<Entity>(); | |
| e1->add_child(e2); | |
| try { | |
| e2->add_child(e1); // throws | |
| } catch (std::runtime_error &err) { | |
| std::cout << "hell yeah, it threw: " << err.what() << '\n'; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <memory> | |
| #include <type_traits> | |
| template <typename T, typename... Ts> | |
| struct is_one_of; | |
| template <typename T, typename... Ts> | |
| struct is_one_of<T, T, Ts...> : std::true_type {}; | |
| template <typename T> | |
| struct is_one_of<T> : std::false_type {}; | |
| template <typename T, typename R, typename... Ts> | |
| struct is_one_of<T, R, Ts...> : is_one_of<T, Ts...> {}; | |
| template <typename T, typename... Ts> | |
| concept one_of = is_one_of<T, Ts...>::value; | |
| template <typename Me, typename... Parents> | |
| class HasParent; | |
| template <typename Me, typename... Children> | |
| class HasChildren; | |
| template <class Child, class Parent> | |
| concept can_set_parent = requires(Child c, std::shared_ptr<Parent> p) { | |
| { c.set_parent(p) }; | |
| }; | |
| template <class Child, class Parent> | |
| concept can_check_parent = requires(Child c, std::shared_ptr<Parent> p) { | |
| { c.check_parent(p) } -> std::same_as<bool>; | |
| }; | |
| template <typename...> | |
| struct is_unique; | |
| template <typename T> | |
| struct is_unique<T> : std::true_type {}; | |
| template <typename T, typename... Ts> | |
| struct is_unique<T, Ts...> { | |
| constexpr static bool value = | |
| (!std::same_as<T, Ts> && ... && is_unique<Ts...>::value); | |
| }; | |
| template <typename... Ts> | |
| concept unique = is_unique<Ts...>::value; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment