From f4708e78442f4185585d18954fed9659f5bc4e3b Mon Sep 17 00:00:00 2001
From: Rainer Kartmann <rainer.kartmann@kit.edu>
Date: Wed, 11 Aug 2021 10:45:46 +0200
Subject: [PATCH] Revise get/find* interface of memory base classes. Remove
 EntityContainerBase

---
 .../RobotAPI/libraries/armem/CMakeLists.txt   |   4 +-
 .../armem/core/base/CoreSegmentBase.h         | 195 ++++++++-----
 .../libraries/armem/core/base/EntityBase.h    | 129 +++++----
 .../armem/core/base/EntityInstanceBase.h      |   7 +-
 .../armem/core/base/EntitySnapshotBase.h      |  91 ++++--
 .../libraries/armem/core/base/MemoryBase.h    | 149 ++++------
 .../armem/core/base/ProviderSegmentBase.h     | 106 ++++---
 .../core/base/detail/EntityContainerBase.cpp  |   1 -
 .../core/base/detail/EntityContainerBase.h    | 124 ---------
 .../armem/core/base/detail/lookup_mixins.cpp  |  63 +++++
 .../armem/core/base/detail/lookup_mixins.h    | 260 ++++++++++++++++++
 11 files changed, 726 insertions(+), 403 deletions(-)
 delete mode 100644 source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.cpp
 delete mode 100644 source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.h
 create mode 100644 source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.cpp
 create mode 100644 source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.h

diff --git a/source/RobotAPI/libraries/armem/CMakeLists.txt b/source/RobotAPI/libraries/armem/CMakeLists.txt
index b08141a6e..81a6ad2cf 100644
--- a/source/RobotAPI/libraries/armem/CMakeLists.txt
+++ b/source/RobotAPI/libraries/armem/CMakeLists.txt
@@ -34,9 +34,9 @@ set(LIB_FILES
 
     core/base/detail/MemoryItem.cpp
     core/base/detail/MemoryContainerBase.cpp
-    core/base/detail/EntityContainerBase.cpp
     core/base/detail/AronTyped.cpp
     core/base/detail/iteration_mixins.cpp
+    core/base/detail/lookup_mixins.cpp
     core/base/detail/negative_index_semantics.cpp
 
     # core/base/CoreSegmentBase.cpp
@@ -140,9 +140,9 @@ set(LIB_HEADERS
 
     core/base/detail/MemoryItem.h
     core/base/detail/MemoryContainerBase.h
-    core/base/detail/EntityContainerBase.h
     core/base/detail/AronTyped.h
     core/base/detail/iteration_mixins.h
+    core/base/detail/lookup_mixins.h
     core/base/detail/negative_index_semantics.h
 
     core/base/CoreSegmentBase.h
diff --git a/source/RobotAPI/libraries/armem/core/base/CoreSegmentBase.h b/source/RobotAPI/libraries/armem/core/base/CoreSegmentBase.h
index 0de79cccd..06833c954 100644
--- a/source/RobotAPI/libraries/armem/core/base/CoreSegmentBase.h
+++ b/source/RobotAPI/libraries/armem/core/base/CoreSegmentBase.h
@@ -5,8 +5,9 @@
 
 #include "ProviderSegmentBase.h"
 #include "detail/AronTyped.h"
-#include "detail/EntityContainerBase.h"
+#include "detail/MemoryContainerBase.h"
 #include "detail/iteration_mixins.h"
+#include "detail/lookup_mixins.h"
 
 
 namespace armarx::armem::base
@@ -17,13 +18,16 @@ namespace armarx::armem::base
      */
     template <class _ProviderSegmentT, class _Derived>
     class CoreSegmentBase :
-        public detail::EntityContainerBase<_ProviderSegmentT, typename _ProviderSegmentT::EntityT, _Derived>,
-        public detail::AronTyped,
-        public detail::ForEachEntityInstanceMixin<_Derived>,
-        public detail::ForEachEntitySnapshotMixin<_Derived>,
-        public detail::ForEachEntityMixin<_Derived>
+        public detail::MemoryContainerBase<std::map<std::string, _ProviderSegmentT>, _Derived>
+        , public detail::AronTyped
+        , public detail::ForEachEntityInstanceMixin<_Derived>
+        , public detail::ForEachEntitySnapshotMixin<_Derived>
+        , public detail::ForEachEntityMixin<_Derived>
+        , public detail::GetFindInstanceMixin<_Derived>
+        , public detail::GetFindSnapshotMixin<_Derived>
+        , public detail::GetFindEntityMixin<_Derived>
     {
-        using Base = detail::EntityContainerBase<_ProviderSegmentT, typename _ProviderSegmentT::EntityT, _Derived>;
+        using Base = detail::MemoryContainerBase<std::map<std::string, _ProviderSegmentT>, _Derived>;
 
     public:
 
@@ -35,6 +39,9 @@ namespace armarx::armem::base
         using EntitySnapshotT = typename EntityT::EntitySnapshotT;
         using EntityInstanceT = typename EntitySnapshotT::EntityInstanceT;
 
+        using ChildT = ProviderSegmentT;
+
+
         struct UpdateResult
         {
             armarx::armem::UpdateType coreSegmentUpdateType;
@@ -80,69 +87,90 @@ namespace armarx::armem::base
 
         // READ ACCESS
 
-        inline const std::string& name() const
+        // Get key
+        inline std::string& name()
         {
             return this->id().coreSegmentName;
         }
-        inline std::string& name()
+        inline const std::string& name() const
         {
-            return const_cast<std::string&>(const_cast<const CoreSegmentBase*>(this)->name());
+            return this->id().coreSegmentName;
         }
 
 
+        // Has child by key
         bool hasProviderSegment(const std::string& name) const
         {
-            return this->_container.count(name) > 0;
+            return this->findProviderSegment(name) != nullptr;
+        }
+        // Has child by memory ID
+        bool hasProviderSegment(const MemoryID& providerSegmentID) const
+        {
+            return this->findProviderSegment(providerSegmentID) != nullptr;
         }
 
-        std::vector<std::string> getProviderSegmentNames() const
+
+        // Find child by key
+        ProviderSegmentT* findProviderSegment(const std::string& name)
         {
-            return simox::alg::get_keys(this->_container);
+            return detail::findChildByKey(name, this->_container);
+        }
+        const ProviderSegmentT* findProviderSegment(const std::string& name) const
+        {
+            return detail::findChildByKey(name, this->_container);
         }
 
+        // Get child by key
         ProviderSegmentT& getProviderSegment(const std::string& name)
         {
-            return const_cast<ProviderSegmentT&>(const_cast<const CoreSegmentBase*>(this)->getProviderSegment(name));
+            return detail::getChildByKey(name, this->_container, *this);
         }
-
         const ProviderSegmentT& getProviderSegment(const std::string& name) const
         {
-            auto it = this->_container.find(name);
-            if (it != this->_container.end())
-            {
-                return it->second;
-            }
-            else
-            {
-                throw armem::error::MissingEntry::create<ProviderSegmentT>(name, *this);
-            }
+            return detail::getChildByKey(name, this->_container, *this);
         }
 
-        using Base::getEntity;
-        const EntityT& getEntity(const MemoryID& id) const
+        // Find child by MemoryID
+        ProviderSegmentT* findProviderSegment(const MemoryID& providerSegmentID)
         {
-            this->_checkContainerName(id.coreSegmentName, this->getKeyString());
-            return getProviderSegment(id.providerSegmentName).getEntity(id);
+            detail::checkHasProviderSegmentName(providerSegmentID);
+            return this->findProviderSegment(providerSegmentID.providerSegmentName);
+        }
+        const ProviderSegmentT* findProviderSegment(const MemoryID& providerSegmentID) const
+        {
+            detail::checkHasProviderSegmentName(providerSegmentID);
+            return this->findProviderSegment(providerSegmentID.providerSegmentName);
         }
 
-        const EntityT* findEntity(const MemoryID& id) const
+        // Get child by MemoryID
+        ProviderSegmentT& getProviderSegment(const MemoryID& providerSegmentID)
         {
-            this->_checkContainerName(id.coreSegmentName, this->getKeyString());
-            if (id.hasProviderSegmentName())
-            {
-                return getProviderSegment(id.providerSegmentName).findEntity(id);
-            }
-            else
-            {
-                for (const auto& [_, providerSegment] : this->_container)
-                {
-                    if (auto entity = providerSegment.findEntity(id))
-                    {
-                        return entity;
-                    }
-                }
-                return nullptr;
-            }
+            detail::checkHasProviderSegmentName(providerSegmentID);
+            return this->getProviderSegment(providerSegmentID.providerSegmentName);
+        }
+        const ProviderSegmentT& getProviderSegment(const MemoryID& providerSegmentID) const
+        {
+            detail::checkHasProviderSegmentName(providerSegmentID);
+            return this->getProviderSegment(providerSegmentID.providerSegmentName);
+        }
+
+        // get/findInstance are provided by GetFindInstanceMixin
+        // get/findSnapshot are provided by GetFindSnapshotMixin
+        // get/findEntity are provided by GetFindEntityMixin
+
+        // Search all provider segments for the first matching entity.
+
+        bool hasEntity(const std::string& entityName) const
+        {
+            return this->findEntity(entityName) != nullptr;
+        }
+        EntityT* findEntity(const std::string& entityName)
+        {
+            return _findEntity(*this, entityName);
+        }
+        const EntityT* findEntity(const std::string& entityName) const
+        {
+            return _findEntity(*this, entityName);
         }
 
 
@@ -170,6 +198,13 @@ namespace armarx::armem::base
         // forEachInstance() is provided by ForEachEntityInstanceMixin.
 
 
+        // Get child keys
+        std::vector<std::string> getProviderSegmentNames() const
+        {
+            return simox::alg::get_keys(this->_container);
+        }
+
+
         [[deprecated("Direct container access is deprecated. Use forEach*() instead.")]]
         inline const auto& providerSegments() const
         {
@@ -193,7 +228,7 @@ namespace armarx::armem::base
         {
             this->_checkContainerName(update.entityID.coreSegmentName, this->name());
 
-            auto [inserted, provSeg] = addProviderSegmentIfMissing(update.entityID.providerSegmentName);
+            auto [inserted, provSeg] = _addProviderSegmentIfMissing(update.entityID.providerSegmentName);
 
 
             // Update entry.
@@ -209,30 +244,6 @@ namespace armarx::armem::base
             return ret;
         }
 
-        std::pair<bool, ProviderSegmentT*> addProviderSegmentIfMissing(const std::string& providerSegmentName)
-        {
-            ProviderSegmentT* provSeg;
-
-            auto it = this->_container.find(providerSegmentName);
-            if (it == this->_container.end())
-            {
-                if (_addMissingProviderSegmentDuringUpdate)
-                {
-                    // Insert into map.
-                    provSeg = &addProviderSegment(providerSegmentName);
-                    return {true, provSeg};
-                }
-                else
-                {
-                    throw error::MissingEntry::create<ProviderSegmentT>(providerSegmentName, *this);
-                }
-            }
-            else
-            {
-                provSeg = &it->second;
-                return {false, provSeg};
-            }
-        }
 
         void append(const _Derived& m)
         {
@@ -317,6 +328,50 @@ namespace armarx::armem::base
         }
 
 
+    protected:
+
+        template <class ParentT>
+        static
+        auto*
+        _findEntity(ParentT&& parent, const std::string& entityName)
+        {
+            decltype(parent.findEntity(entityName)) result = nullptr;
+            parent.forEachProviderSegment([&result, &entityName](auto & provSeg)
+            {
+                result = provSeg.findEntity(entityName);
+                return result == nullptr;  // Keep going if null, break if not null.
+            });
+            return result;
+        }
+
+
+        std::pair<bool, ProviderSegmentT*>
+        _addProviderSegmentIfMissing(const std::string& providerSegmentName)
+        {
+            ProviderSegmentT* provSeg;
+
+            auto it = this->_container.find(providerSegmentName);
+            if (it == this->_container.end())
+            {
+                if (_addMissingProviderSegmentDuringUpdate)
+                {
+                    // Insert into map.
+                    provSeg = &addProviderSegment(providerSegmentName);
+                    return {true, provSeg};
+                }
+                else
+                {
+                    throw error::MissingEntry::create<ProviderSegmentT>(providerSegmentName, *this);
+                }
+            }
+            else
+            {
+                provSeg = &it->second;
+                return {false, provSeg};
+            }
+        }
+
+
     private:
 
         bool _addMissingProviderSegmentDuringUpdate = true;
diff --git a/source/RobotAPI/libraries/armem/core/base/EntityBase.h b/source/RobotAPI/libraries/armem/core/base/EntityBase.h
index 05cbf780d..6f23cde75 100644
--- a/source/RobotAPI/libraries/armem/core/base/EntityBase.h
+++ b/source/RobotAPI/libraries/armem/core/base/EntityBase.h
@@ -11,6 +11,7 @@
 #include "EntitySnapshotBase.h"
 #include "detail/MemoryContainerBase.h"
 #include "detail/iteration_mixins.h"
+#include "detail/lookup_mixins.h"
 #include "detail/negative_index_semantics.h"
 
 
@@ -38,8 +39,9 @@ namespace armarx::armem::base
      */
     template <class _EntitySnapshotT, class _Derived>
     class EntityBase :
-        public detail::MemoryContainerBase<std::map<Time, _EntitySnapshotT>, _Derived>,
-        public detail::ForEachEntityInstanceMixin<_Derived>
+        public detail::MemoryContainerBase<std::map<Time, _EntitySnapshotT>, _Derived>
+        , public detail::ForEachEntityInstanceMixin<_Derived>
+        , public detail::GetFindInstanceMixin<_Derived>
     {
         using Base = detail::MemoryContainerBase<std::map<Time, _EntitySnapshotT>, _Derived>;
 
@@ -51,6 +53,8 @@ namespace armarx::armem::base
         using EntitySnapshotT = _EntitySnapshotT;
         using EntityInstanceT = typename EntitySnapshotT::EntityInstanceT;
 
+        using ChildT = EntitySnapshotT;
+
         struct UpdateResult
         {
             armarx::armem::UpdateType entityUpdateType;
@@ -83,50 +87,43 @@ namespace armarx::armem::base
 
         // READING
 
-        inline const std::string& name() const
+        // Get key
+        inline std::string& name()
         {
             return this->id().entityName;
         }
-        inline std::string& name()
+        inline const std::string& name() const
         {
-            return const_cast<std::string&>(const_cast<const EntityBase*>(this)->name());
+            return this->id().entityName;
         }
 
-        /**
-         * @brief Indicates whether a history entry for the given time exists.
-         */
-        bool hasSnapshot(const Time& time)
-        {
-            return const_cast<const EntityBase*>(this)->hasSnapshot(time);
-        }
+
+        // Has child by key
+        /// Indicates whether a history entry for the given time exists.
         bool hasSnapshot(const Time& time) const
         {
-            return this->_container.count(time) > 0;
+            return this->findSnapshot(time) != nullptr;
         }
-
-        /**
-         * @brief Get the latest timestamp.
-         * @throw `armem::error::EntityHistoryEmpty` If the history is empty.
-         */
-        Time getLatestTimestamp()
+        // Has child by MemoryID
+        bool hasSnapshot(const MemoryID& snapshotID) const
         {
-            return const_cast<const EntityBase*>(this)->getLatestTimestamp();
+            return this->findSnapshot(snapshotID) != nullptr;
         }
 
-        Time getLatestTimestamp() const
+
+        // Find child via key
+        EntitySnapshotT*
+        findSnapshot(const Time& timestamp)
         {
-            return getLatestItem().first;
+            return detail::findChildByKey(timestamp, this->_container);
         }
-
-        /**
-         * @brief Get all timestamps in the history.
-         */
-        std::vector<Time> getTimestamps() const
+        const EntitySnapshotT*
+        findSnapshot(const Time& timestamp) const
         {
-            return simox::alg::get_keys(this->_container);
+            return detail::findChildByKey(timestamp, this->_container);
         }
 
-
+        // Get child via key
         /**
          * @brief Get a snapshot.
          * @param time The time.
@@ -134,33 +131,64 @@ namespace armarx::armem::base
          *
          * @throws `armem::error::MissingEntry` If there is no such entry.
          */
-        EntitySnapshotT& getSnapshot(const Time& time)
+        EntitySnapshotT&
+        getSnapshot(const Time& time)
         {
-            return const_cast<EntitySnapshotT&>(const_cast<const EntityBase*>(this)->getSnapshot(time));
+            return detail::getChildByKey(time, this->_container, *this, [](const Time & time)
+            {
+                return toDateTimeMilliSeconds(time);
+            });
         }
-
-        const EntitySnapshotT& getSnapshot(const Time& time) const
+        const EntitySnapshotT&
+        getSnapshot(const Time& time) const
         {
-            auto it = this->_container.find(time);
-            if (it != this->_container.end())
+            return detail::getChildByKey(time, this->_container, *this, [](const Time & time)
             {
-                return it->second;
-            }
-            else
-            {
-                throw armem::error::MissingEntry::create<EntitySnapshotT>(toDateTimeMilliSeconds(time), *this);
-            }
+                return toDateTimeMilliSeconds(time);
+            });
+        }
+
+        // Find child via MemoryID
+        EntitySnapshotT*
+        findSnapshot(const MemoryID& snapshotID)
+        {
+            detail::checkHasTimestamp(snapshotID);
+            return this->findSnapshot(snapshotID.timestamp);
+        }
+        const EntitySnapshotT*
+        findSnapshot(const MemoryID& snapshotID) const
+        {
+            detail::checkHasTimestamp(snapshotID);
+            return this->findSnapshot(snapshotID.timestamp);
         }
 
-        EntitySnapshotT& getSnapshot(const MemoryID& id)
+        // Get child via MemoryID
+        EntitySnapshotT&
+        getSnapshot(const MemoryID& snapshotID)
         {
-            return const_cast<EntitySnapshotT&>(const_cast<const EntityBase*>(this)->getSnapshot(id));
+            detail::checkHasTimestamp(snapshotID);
+            return this->getSnapshot(snapshotID.timestamp);
         }
+        const EntitySnapshotT&
+        getSnapshot(const MemoryID& snapshotID) const
+        {
+            detail::checkHasTimestamp(snapshotID);
+            return this->getSnapshot(snapshotID.timestamp);
+
+        }
+
+        // get/findInstance are provided by GetFindInstanceMixin
+
 
-        const EntitySnapshotT& getSnapshot(const MemoryID& id) const
+        // More getter/finder for snapshots
+
+        /**
+         * @brief Get the latest timestamp.
+         * @throw `armem::error::EntityHistoryEmpty` If the history is empty.
+         */
+        Time getLatestTimestamp() const
         {
-            this->_checkContainerName(id.entityName, this->name());
-            return getSnapshot(id.timestamp);
+            return getLatestItem().first;
         }
 
         /**
@@ -386,6 +414,14 @@ namespace armarx::armem::base
         }
 
 
+        // Get child keys
+        /// @brief Get all timestamps in the history.
+        std::vector<Time> getTimestamps() const
+        {
+            return simox::alg::get_keys(this->_container);
+        }
+
+
         [[deprecated("Direct container access is deprecated. Use forEach*() instead.")]]
         inline const ContainerT& history() const
         {
@@ -541,7 +577,6 @@ namespace armarx::armem::base
             return *this->_container.begin();
         }
 
-
     };
 
 }
diff --git a/source/RobotAPI/libraries/armem/core/base/EntityInstanceBase.h b/source/RobotAPI/libraries/armem/core/base/EntityInstanceBase.h
index fcb1869f7..8f7b8aafc 100644
--- a/source/RobotAPI/libraries/armem/core/base/EntityInstanceBase.h
+++ b/source/RobotAPI/libraries/armem/core/base/EntityInstanceBase.h
@@ -72,6 +72,7 @@ namespace armarx::armem::base
         }
 
 
+        // Key
         inline int& index()
         {
             return id().instanceIndex;
@@ -82,6 +83,8 @@ namespace armarx::armem::base
         }
 
 
+        // Data
+
         EntityInstanceMetadata& metadata()
         {
             return _metadata;
@@ -102,6 +105,8 @@ namespace armarx::armem::base
         }
 
 
+        // Misc
+
         static std::string getLevelName()
         {
             return "entity instance";
@@ -113,7 +118,6 @@ namespace armarx::armem::base
         }
 
 
-
     protected:
 
         /// The metadata.
@@ -122,7 +126,6 @@ namespace armarx::armem::base
         /// The data. May be nullptr.
         DataT _data;
 
-
     };
 
 }
diff --git a/source/RobotAPI/libraries/armem/core/base/EntitySnapshotBase.h b/source/RobotAPI/libraries/armem/core/base/EntitySnapshotBase.h
index 765313644..0d0f091f5 100644
--- a/source/RobotAPI/libraries/armem/core/base/EntitySnapshotBase.h
+++ b/source/RobotAPI/libraries/armem/core/base/EntitySnapshotBase.h
@@ -8,6 +8,7 @@
 #include "EntityInstanceBase.h"
 #include "detail/MemoryContainerBase.h"
 #include "detail/iteration_mixins.h"
+#include "detail/lookup_mixins.h"
 
 
 namespace armarx::armem::base::detail
@@ -55,37 +56,60 @@ namespace armarx::armem::base
 
         // READING
 
+        // Get key
         inline Time& time()
         {
             return this->id().timestamp;
         }
-
         inline const Time& time() const
         {
             return this->id().timestamp;
         }
 
+
+        // Has child by key
         bool hasInstance(int index) const
         {
-            size_t si = size_t(index);
-            return index >= 0 && si < this->_container.size();
+            return this->findInstance(index) != nullptr;
+        }
+        // Has child by ID
+        bool hasInstance(const MemoryID& instanceID) const
+        {
+            return this->findInstance(instanceID) != nullptr;
+        }
+
+
+        // Find child by key
+        EntityInstanceT* findInstance(int index)
+        {
+            return const_cast<EntityInstanceT*>(const_cast<const EntitySnapshotBase*>(this)->findInstance(index));
+        }
+        const EntityInstanceT* findInstance(int index) const
+        {
+            const size_t si = static_cast<size_t>(index);
+            return (index >= 0 && si < this->_container.size())
+                   ? &this->_container[si]
+                   : nullptr;
         }
 
+        // Get child by key
         /**
          * @brief Get the given instance.
          * @param index The instance's index.
          * @return The instance.
          * @throw `armem::error::MissingEntry` If the given index is invalid.
          */
-        EntityInstanceT& getInstance(int index)
+        EntityInstanceT&
+        getInstance(int index)
         {
             return const_cast<EntityInstanceT&>(const_cast<const EntitySnapshotBase*>(this)->getInstance(index));
         }
-        const EntityInstanceT& getInstance(int index) const
+        const EntityInstanceT&
+        getInstance(int index) const
         {
-            if (hasInstance(index))
+            if (const EntityInstanceT* instance = findInstance(index))
             {
-                return this->_container[static_cast<size_t>(index)];
+                return *instance;
             }
             else
             {
@@ -93,23 +117,21 @@ namespace armarx::armem::base
             }
         }
 
-        EntityInstanceT* findInstance(int index)
+        // Find child by MemoryID
+        EntityInstanceT*
+        findInstance(const MemoryID& instanceID)
         {
-            return const_cast<EntityInstanceT*>(const_cast<const EntitySnapshotBase*>(this)->findInstance(index));
+            detail::checkHasInstanceIndex(instanceID);
+            return this->findInstance(instanceID.instanceIndex);
         }
-        const EntityInstanceT* findInstance(int index) const
+        const EntityInstanceT*
+        findInstance(const MemoryID& instanceID) const
         {
-            if (hasInstance(index))
-            {
-                return &this->_container[static_cast<size_t>(index)];
-            }
-            else
-            {
-                return nullptr;
-            }
+            detail::checkHasInstanceIndex(instanceID);
+            return this->findInstance(instanceID.instanceIndex);
         }
 
-
+        // Get child by MemoryID
         /**
          * @brief Get the given instance.
          * @param index The instance's index.
@@ -117,18 +139,17 @@ namespace armarx::armem::base
          * @throw `armem::error::MissingEntry` If the given index is invalid.
          * @throw `armem::error::InvalidMemoryID` If memory ID does not have an instance index.
          */
-        EntityInstanceT& getInstance(const MemoryID& id)
+        EntityInstanceT&
+        getInstance(const MemoryID& instanceID)
         {
-            return const_cast<EntityInstanceT&>(const_cast<const EntitySnapshotBase*>(this)->getInstance(id));
+            detail::checkHasInstanceIndex(instanceID);
+            return this->getInstance(instanceID.instanceIndex);
         }
-
-        const EntityInstanceT& getInstance(const MemoryID& id) const
+        const EntityInstanceT&
+        getInstance(const MemoryID& instanceID) const
         {
-            if (!id.hasInstanceIndex())
-            {
-                throw armem::error::InvalidMemoryID(id, "ID has no instance index.");
-            }
-            return getInstance(id.instanceIndex);
+            detail::checkHasInstanceIndex(instanceID);
+            return this->getInstance(instanceID.instanceIndex);
         }
 
 
@@ -151,6 +172,20 @@ namespace armarx::armem::base
             return this->forEachChild(func);
         }
 
+
+        // Get child keys
+        std::vector<int> getInstanceIndices() const
+        {
+            std::vector<int> indices;
+            indices.reserve(this->size());
+            for (size_t i = 0; i < this->size(); ++i)
+            {
+                indices.push_back(static_cast<int>(i));
+            }
+            return indices;
+        }
+
+
         [[deprecated("Direct container access is deprecated. Use forEach*() instead.")]]
         inline const std::vector<EntityInstanceT>& instances() const
         {
diff --git a/source/RobotAPI/libraries/armem/core/base/MemoryBase.h b/source/RobotAPI/libraries/armem/core/base/MemoryBase.h
index 9865f6da1..f4699b6ac 100644
--- a/source/RobotAPI/libraries/armem/core/base/MemoryBase.h
+++ b/source/RobotAPI/libraries/armem/core/base/MemoryBase.h
@@ -4,8 +4,9 @@
 #include <string>
 
 #include "CoreSegmentBase.h"
-#include "detail/EntityContainerBase.h"
+#include "detail/MemoryContainerBase.h"
 #include "detail/iteration_mixins.h"
+#include "detail/lookup_mixins.h"
 
 
 namespace armarx::armem::base
@@ -16,13 +17,17 @@ namespace armarx::armem::base
      */
     template <class _CoreSegmentT, class _Derived>
     class MemoryBase :
-        public detail::EntityContainerBase<_CoreSegmentT, typename _CoreSegmentT::ProviderSegmentT::EntityT, _Derived>,
-        public detail::ForEachEntityInstanceMixin<_Derived>,
-        public detail::ForEachEntitySnapshotMixin<_Derived>,
-        public detail::ForEachEntityMixin<_Derived>,
-        public detail::ForEachProviderSegmentMixin<_Derived>
+        public detail::MemoryContainerBase<std::map<std::string, _CoreSegmentT>, _Derived>
+        , public detail::ForEachEntityInstanceMixin<_Derived>
+        , public detail::ForEachEntitySnapshotMixin<_Derived>
+        , public detail::ForEachEntityMixin<_Derived>
+        , public detail::ForEachProviderSegmentMixin<_Derived>
+        , public detail::GetFindInstanceMixin<_Derived>
+        , public detail::GetFindSnapshotMixin<_Derived>
+        , public detail::GetFindEntityMixin<_Derived>
+        , public detail::GetFindProviderSegmentMixin<_Derived>
     {
-        using Base = detail::EntityContainerBase<_CoreSegmentT, typename _CoreSegmentT::ProviderSegmentT::EntityT, _Derived>;
+        using Base = detail::MemoryContainerBase<std::map<std::string, _CoreSegmentT>, _Derived>;
 
     public:
 
@@ -35,6 +40,8 @@ namespace armarx::armem::base
         using EntitySnapshotT = typename EntityT::EntitySnapshotT;
         using EntityInstanceT = typename EntitySnapshotT::EntityInstanceT;
 
+        using ChildT = CoreSegmentT;
+
 
         struct UpdateResult
         {
@@ -77,95 +84,77 @@ namespace armarx::armem::base
 
         // READ ACCESS
 
-        inline const std::string& name() const
+        // Get key
+        inline std::string& name()
         {
             return this->id().memoryName;
         }
-        inline std::string& name()
+        inline const std::string& name() const
         {
-            return const_cast<std::string&>(const_cast<const MemoryBase*>(this)->name());
+            return this->id().memoryName;
         }
 
 
+        // Has child by key
         bool hasCoreSegment(const std::string& name) const
         {
-            return this->_container.count(name) > 0;
+            return this->findCoreSegment(name) != nullptr;
+        }
+        // Has child by MemoryID
+        bool hasCoreSegment(const MemoryID& coreSegmentID) const
+        {
+            return this->findCoreSegment(coreSegmentID) != nullptr;
         }
 
-        std::vector<std::string> getCoreSegmentNames() const
+        // Find child by key
+        CoreSegmentT* findCoreSegment(const std::string& name)
         {
-            return simox::alg::get_keys(this->_container);
+            return detail::findChildByKey(name, this->_container);
+        }
+        const CoreSegmentT* findCoreSegment(const std::string& name) const
+        {
+            return detail::findChildByKey(name, this->_container);
         }
 
+        // Get child by key
         CoreSegmentT& getCoreSegment(const std::string& name)
         {
-            return const_cast<CoreSegmentT&>(const_cast<const MemoryBase*>(this)->getCoreSegment(name));
+            return detail::getChildByKey(name, this->_container, *this);
         }
-
         const CoreSegmentT& getCoreSegment(const std::string& name) const
         {
-            auto it = this->_container.find(name);
-            if (it != this->_container.end())
-            {
-                return it->second;
-            }
-            else
-            {
-                throw armem::error::MissingEntry::create<CoreSegmentT>(name, *this);
-            }
+            return detail::getChildByKey(name, this->_container, *this);
         }
 
-
-        bool hasProviderSegment(const MemoryID& providerSegmentID) const
+        // Find child by MemoryID
+        CoreSegmentT* findCoreSegment(const MemoryID& coreSegmentID)
         {
-            auto it = this->_container.find(providerSegmentID.coreSegmentName);
-            if (it != this->_container.end())
-            {
-                return it->second.hasProviderSegment(providerSegmentID.providerSegmentName);
-            }
-            else
-            {
-                return false;
-            }
+            detail::checkHasCoreSegmentName(coreSegmentID);
+            return this->findCoreSegment(coreSegmentID.providerSegmentName);
         }
-
-        ProviderSegmentT& getProviderSegment(const MemoryID& providerSegmentID)
+        const CoreSegmentT* findCoreSegment(const MemoryID& coreSegmentID) const
         {
-            return getCoreSegment(providerSegmentID.coreSegmentName).getProviderSegment(providerSegmentID.providerSegmentName);
+            detail::checkHasCoreSegmentName(coreSegmentID);
+            return this->findCoreSegment(coreSegmentID.providerSegmentName);
         }
 
-        const ProviderSegmentT& getProviderSegment(const MemoryID& providerSegmentID) const
+        // Get child by MemoryID
+        CoreSegmentT& getCoreSegment(const MemoryID& coreSegmentID)
         {
-            return getCoreSegment(providerSegmentID.coreSegmentName).getProviderSegment(providerSegmentID.providerSegmentName);
+            detail::checkHasCoreSegmentName(coreSegmentID);
+            return this->getCoreSegment(coreSegmentID.providerSegmentName);
         }
-
-
-        using Base::getEntity;
-        const EntityT& getEntity(const MemoryID& id) const
+        const CoreSegmentT& getCoreSegment(const MemoryID& coreSegmentID) const
         {
-            this->_checkContainerName(id.memoryName, this->name());
-            return getCoreSegment(id.coreSegmentName).getEntity(id);
+            detail::checkHasCoreSegmentName(coreSegmentID);
+            return this->getCoreSegment(coreSegmentID.providerSegmentName);
         }
 
-        const EntityT* findEntity(const MemoryID& id) const
-        {
-            this->_checkContainerName(id.memoryName, this->name());
-            if (id.hasCoreSegmentName())
-            {
-                return getCoreSegment(id.coreSegmentName).findEntity(id);
-            }
-            else
-            {
-                for (const auto& [_, coreSegment] : this->_container)
-                {
-                    if (auto entity = coreSegment.findEntity(id))
-                    {
-                        return entity;
-                    }
-                }
-                return nullptr;
-            }
-        }
+        // get/findInstance are provided by GetFindInstanceMixin
+        // get/findSnapshot are provided by GetFindSnapshotMixin
+        // get/findEntity are provided by GetFindEntityMixin
+        // get/findProviderSegment are provided by GetFindProviderSegmentMixin
+
 
 
         // ITERATION
@@ -193,6 +182,12 @@ namespace armarx::armem::base
         // forEachInstance() is provided by ForEachEntityInstanceMixin.
 
 
+        std::vector<std::string> getCoreSegmentNames() const
+        {
+            return simox::alg::get_keys(this->_container);
+        }
+
+
         [[deprecated("Direct container access is deprecated. Use forEach*() instead.")]]
         inline auto& coreSegments() const
         {
@@ -229,28 +224,6 @@ namespace armarx::armem::base
             return _addCoreSegment(coreSegment.name(), coreSegment);
         }
 
-        /**
-         * @brief Add multiple core segments.
-         * @param The core segment names.
-         * @return The core segments. The contained pointers are never null.
-         */
-        std::vector<CoreSegmentT*> addCoreSegments(const std::vector<std::string>& names)
-        {
-            std::vector<CoreSegmentT*> segments;
-            for (const auto& name : names)
-            {
-                try
-                {
-                    segments.push_back(&addCoreSegment(name));
-                }
-                catch (const armem::error::ContainerEntryAlreadyExists& e)
-                {
-                    // ARMARX_INFO << e.what() << "\nIgnoring multiple addition.";
-                }
-            }
-            return segments;
-        }
-
 
         /**
          * @brief Store all updates in `commit`.
@@ -394,7 +367,7 @@ namespace armarx::armem::base
 
         std::pair<bool, CoreSegmentT*> _addCoreSegmentIfMissing(const std::string& coreSegmentName)
         {
-            CoreSegmentT* coreSeg;
+            CoreSegmentT* coreSeg = nullptr;
 
             auto it = this->_container.find(coreSegmentName);
             if (it == this->_container.end())
diff --git a/source/RobotAPI/libraries/armem/core/base/ProviderSegmentBase.h b/source/RobotAPI/libraries/armem/core/base/ProviderSegmentBase.h
index e51632417..2f76aa839 100644
--- a/source/RobotAPI/libraries/armem/core/base/ProviderSegmentBase.h
+++ b/source/RobotAPI/libraries/armem/core/base/ProviderSegmentBase.h
@@ -5,8 +5,9 @@
 
 #include "EntityBase.h"
 #include "detail/AronTyped.h"
-#include "detail/EntityContainerBase.h"
+#include "detail/MemoryContainerBase.h"
 #include "detail/iteration_mixins.h"
+#include "detail/lookup_mixins.h"
 
 
 namespace armarx::armem::base
@@ -17,12 +18,14 @@ namespace armarx::armem::base
      */
     template <class _EntityT, class _Derived>
     class ProviderSegmentBase :
-        public detail::EntityContainerBase<_EntityT, _EntityT, _Derived>,
-        public detail::AronTyped,
-        public detail::ForEachEntityInstanceMixin<_Derived>,
-        public detail::ForEachEntitySnapshotMixin<_Derived>
+        public detail::MemoryContainerBase<std::map<std::string, _EntityT>, _Derived>
+        , public detail::AronTyped
+        , public detail::ForEachEntityInstanceMixin<_Derived>
+        , public detail::ForEachEntitySnapshotMixin<_Derived>
+        , public detail::GetFindInstanceMixin<_Derived>
+        , public detail::GetFindSnapshotMixin<_Derived>
     {
-        using Base = detail::EntityContainerBase<_EntityT, _EntityT, _Derived>;
+        using Base = detail::MemoryContainerBase<std::map<std::string, _EntityT>, _Derived>;
 
     public:
 
@@ -33,6 +36,8 @@ namespace armarx::armem::base
         using EntitySnapshotT = typename EntityT::EntitySnapshotT;
         using EntityInstanceT = typename EntitySnapshotT::EntityInstanceT;
 
+        using ChildT = EntityT;
+
         struct UpdateResult
         {
             armarx::armem::UpdateType providerSegmentUpdateType;
@@ -48,6 +53,7 @@ namespace armarx::armem::base
             {}
         };
 
+
     public:
 
         ProviderSegmentBase()
@@ -76,65 +82,76 @@ namespace armarx::armem::base
 
         // READ ACCESS
 
-        inline const std::string& name() const
+        // Get key
+        inline std::string& name()
         {
             return this->id().providerSegmentName;
         }
-        inline std::string& name()
+        inline const std::string& name() const
         {
-            return const_cast<std::string&>(const_cast<const ProviderSegmentBase*>(this)->name());
+            return this->id().providerSegmentName;
         }
 
 
+        // Has child by key
         bool hasEntity(const std::string& name) const
         {
-            return this->_container.count(name) > 0;
+            return this->findEntity(name) != nullptr;
         }
-
-        std::vector<std::string> getEntityNames() const
+        // Has child by ID
+        bool hasEntity(const MemoryID& entityID) const
         {
-            return simox::alg::get_keys(this->_container);
+            return this->findEntity(entityID) != nullptr;
         }
 
-        using Base::getEntity;
-        const EntityT& getEntity(const MemoryID& id) const
+
+        // Find child by key
+        EntityT* findEntity(const std::string& name)
+        {
+            return detail::findChildByKey(name, this->_container);
+        }
+        const EntityT* findEntity(const std::string& name) const
         {
-            this->_checkContainerName(id.providerSegmentName, this->getKeyString());
-            return getEntity(id.entityName);
+            return detail::findChildByKey(name, this->_container);
         }
 
+        // Get child by key
         EntityT& getEntity(const std::string& name)
         {
-            return const_cast<EntityT&>(const_cast<const ProviderSegmentBase*>(this)->getEntity(name));
+            return detail::getChildByKey(name, this->_container, *this);
         }
-
         const EntityT& getEntity(const std::string& name) const
         {
-            auto it = this->_container.find(name);
-            if (it != this->_container.end())
-            {
-                return it->second;
-            }
-            else
-            {
-                throw armem::error::MissingEntry::create<EntityT>(name, *this);
-            }
+            return detail::getChildByKey(name, this->_container, *this);
         }
 
-        const EntityT* findEntity(const MemoryID& id) const
+        // Find child by MemoryID
+        EntityT* findEntity(const MemoryID& entityID)
         {
-            this->_checkContainerName(id.providerSegmentName, this->getKeyString());
-            auto it = this->_container.find(id.entityName);
-            if (it != this->_container.end())
-            {
-                return &it->second;
-            }
-            else
-            {
-                return nullptr;
-            }
+            detail::checkHasTimestamp(entityID);
+            return this->findEntity(entityID.entityName);
+        }
+        const EntityT* findEntity(const MemoryID& entityID) const
+        {
+            detail::checkHasTimestamp(entityID);
+            return this->findEntity(entityID.entityName);
         }
 
+        // Get child by MemoryID
+        EntityT& getEntity(const MemoryID& entityID)
+        {
+            detail::checkHasTimestamp(entityID);
+            return this->getEntity(entityID.entityName);
+        }
+        const EntityT& getEntity(const MemoryID& entityID) const
+        {
+            detail::checkHasTimestamp(entityID);
+            return this->getEntity(entityID.entityName);
+        }
+
+        // get/findInstance are provided by GetFindInstanceMixin
+        // get/findSnapshot are provided by GetFindSnapshotMixin
+
 
         // ITERATION
 
@@ -157,8 +174,15 @@ namespace armarx::armem::base
             return this->forEachChild(func);
         }
 
-        // forEachSnapshot() is provided by ForEachEntitySnapshotMixin.
-        // forEachInstance() is provided by ForEachEntityInstanceMixin.
+        // forEachSnapshot() is provided by ForEachEntitySnapshotMixin
+        // forEachInstance() is provided by ForEachEntityInstanceMixin
+
+
+        // Get child keys
+        std::vector<std::string> getEntityNames() const
+        {
+            return simox::alg::get_keys(this->_container);
+        }
 
 
         [[deprecated("Direct container access is deprecated. Use forEach*() instead.")]]
diff --git a/source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.cpp b/source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.cpp
deleted file mode 100644
index 05a23bc12..000000000
--- a/source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.cpp
+++ /dev/null
@@ -1 +0,0 @@
-#include "EntityContainerBase.h"
diff --git a/source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.h b/source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.h
deleted file mode 100644
index 53cddfff6..000000000
--- a/source/RobotAPI/libraries/armem/core/base/detail/EntityContainerBase.h
+++ /dev/null
@@ -1,124 +0,0 @@
-#pragma once
-
-#include <RobotAPI/libraries/armem/core/Commit.h>
-#include <RobotAPI/libraries/armem/core/error/ArMemError.h>
-
-#include "MemoryContainerBase.h"
-
-#include <RobotAPI/libraries/armem/core/base/EntityBase.h>
-#include <RobotAPI/libraries/armem/core/base/EntitySnapshotBase.h>
-
-
-namespace armarx::armem::base::detail
-{
-
-    /**
-     * @brief A container of entities at some point in the hierarchy.
-     *
-     * Can be updated by multiple entity updates.
-     */
-    template <class _ValueT, class _EntityT, class _Derived>
-    class EntityContainerBase :
-        public MemoryContainerBase<std::map<std::string, _ValueT>, _Derived>
-    {
-        using Base = MemoryContainerBase<std::map<std::string, _ValueT>, _Derived>;
-
-    public:
-
-        using DerivedT = _Derived;
-        using ValueT = _ValueT;
-
-        using EntityT = _EntityT;
-        using EntitySnapshotT = typename EntityT::EntitySnapshotT;
-        using EntityInstanceT = typename EntitySnapshotT::EntityInstanceT;
-
-    public:
-
-        // `using Base::MemoryContainerBase` breaks code completion of QtCreator.
-        using MemoryContainerBase<std::map<std::string, _ValueT>, _Derived>::MemoryContainerBase;
-        using MemoryContainerBase<std::map<std::string, _ValueT>, _Derived>::operator=;
-
-        /**
-         * @brief Retrieve an entity.
-         * @param id The entity ID.
-         * @return The entity.
-         * @throw An exception deriving from `armem::error::ArMemError` if the entity is missing.
-         */
-        // const EntityT& getEntity(const MemoryID& id) const;
-        EntityT& getEntity(const MemoryID& id)
-        {
-            return const_cast<EntityT&>(const_cast<const EntityContainerBase*>(this)->_derived().getEntity(id));
-        }
-
-        /**
-         * @brief Find an entity.
-         *
-         * Search for the entity with the given ID and return a pointer to the
-         * first match. If `id` is underspecified (e.g. no provider segment name),
-         * search all children until the first match is found.
-         *
-         * If no matching entity is found, return `nullptr`.
-         *
-         * @param id The entities ID.
-         * @return A pointer to the first matching entity or `nullptr` if none was found.
-         */
-        // const EntityT* findEntity(const MemoryID& id) const;
-        EntityT* findEntity(const MemoryID& id)
-        {
-            return const_cast<EntityT*>(const_cast<const EntityContainerBase*>(this)->_derived().findEntity(id));
-        }
-
-
-        /**
-         * @brief Retrieve an entity snapshot.
-         *
-         * Uses `getEntity()` to retrieve the respective entity.
-         *
-         * @param id The snapshot ID.
-         * @return The entity snapshot.
-         * @throw An exception deriving from `armem::error::ArMemError` if the snapshot is missing.
-         */
-        EntitySnapshotT& getEntitySnapshot(const MemoryID& id)
-        {
-            return const_cast<EntitySnapshotT&>(const_cast<const EntityContainerBase*>(this)->_derived().getEntitySnapshot(id));
-        }
-
-        const EntitySnapshotT& getEntitySnapshot(const MemoryID& id) const
-        {
-            const EntityT& entity = _derived().getEntity(id);
-
-            if (id.hasTimestamp())
-            {
-                return entity.getSnapshot(id);
-            }
-            else
-            {
-                return entity.getLatestSnapshot();
-            }
-        }
-
-        EntityInstanceT& getEntityInstance(const MemoryID& id)
-        {
-            return getEntitySnapshot(id).getInstance(id);
-        }
-
-        const EntityInstanceT& getEntityInstance(const MemoryID& id) const
-        {
-            return getEntitySnapshot(id).getInstance(id);
-        }
-
-
-    private:
-
-        DerivedT& _derived()
-        {
-            return static_cast<DerivedT&>(*this);
-        }
-        const DerivedT& _derived() const
-        {
-            return static_cast<const DerivedT&>(*this);
-        }
-
-    };
-
-}
diff --git a/source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.cpp b/source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.cpp
new file mode 100644
index 000000000..ceaf41d3c
--- /dev/null
+++ b/source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.cpp
@@ -0,0 +1,63 @@
+#include "lookup_mixins.h"
+
+#include <RobotAPI/libraries/armem/core/error/ArMemError.h>
+
+
+namespace armarx::armem::base
+{
+
+    void detail::checkHasInstanceIndex(const MemoryID& instanceID)
+    {
+        if (not instanceID.hasInstanceIndex())
+        {
+            throw armem::error::InvalidMemoryID(instanceID, "Entity instance ID has no instance index.");
+        }
+    }
+
+
+    void detail::checkHasTimestamp(const MemoryID& snapshotID)
+    {
+        if (not snapshotID.hasTimestamp())
+        {
+            throw armem::error::InvalidMemoryID(snapshotID, "Entity snapshot ID has no timestamp.");
+        }
+    }
+
+
+    void detail::checkHasEntityName(const MemoryID& entityID)
+    {
+        if (not entityID.hasEntityName())
+        {
+            throw armem::error::InvalidMemoryID(entityID, "Entity ID has no entity name.");
+        }
+    }
+
+
+    void detail::checkHasProviderSegmentName(const MemoryID& providerSegmentID)
+    {
+        if (not providerSegmentID.hasEntityName())
+        {
+            throw armem::error::InvalidMemoryID(providerSegmentID, "Provider Segment ID has no provider segment name.");
+        }
+    }
+
+
+    void detail::checkHasCoreSegmentName(const MemoryID& coreSegmentID)
+    {
+        if (not coreSegmentID.hasEntityName())
+        {
+            throw armem::error::InvalidMemoryID(coreSegmentID, "Core Segment ID has no core segment name.");
+        }
+    }
+
+
+    void detail::checkHasMemoryName(const MemoryID& memoryID)
+    {
+        if (not memoryID.hasEntityName())
+        {
+            throw armem::error::InvalidMemoryID(memoryID, "Memory ID has no memory name.");
+        }
+    }
+
+
+}
diff --git a/source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.h b/source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.h
new file mode 100644
index 000000000..8afbd7108
--- /dev/null
+++ b/source/RobotAPI/libraries/armem/core/base/detail/lookup_mixins.h
@@ -0,0 +1,260 @@
+#pragma once
+
+#include <RobotAPI/libraries/armem/core/MemoryID.h>
+#include <RobotAPI/libraries/armem/core/error/ArMemError.h>
+
+
+namespace armarx::armem::base::detail
+{
+
+    void checkHasInstanceIndex(const MemoryID& instanceID);
+    void checkHasTimestamp(const MemoryID& snapshotID);
+    void checkHasEntityName(const MemoryID& entityID);
+    void checkHasProviderSegmentName(const MemoryID& providerSegmentID);
+    void checkHasCoreSegmentName(const MemoryID& coreSegmentID);
+    void checkHasMemoryName(const MemoryID& memory);
+
+
+    template <class KeyT, class ContainerT>
+    auto* findChildByKey(const KeyT& key, ContainerT&& container)
+    {
+        auto it = container.find(key);
+        return it != container.end() ? &it->second : nullptr;
+    }
+
+    template <class KeyT, class ContainerT, class ParentT, class KeyStringFn>
+    auto&
+    getChildByKey(
+        const KeyT& key,
+        ContainerT&& container,
+        const ParentT& parent,
+        KeyStringFn&& keyStringFn)
+    {
+        if (auto* child = findChildByKey(key, container))
+        {
+            return *child;
+        }
+        else
+        {
+            throw armem::error::MissingEntry::create<typename ParentT::ChildT>(keyStringFn(key), parent);
+        }
+    }
+    template <class KeyT, class ContainerT, class ParentT>
+    auto&
+    getChildByKey(
+        const KeyT& key,
+        ContainerT&& container,
+        const ParentT& parent)
+    {
+        return getChildByKey(key, container, parent, [](const KeyT & key)
+        {
+            return key;
+        });
+    }
+
+
+    template <class DerivedT, class ThisT>
+    DerivedT&
+    derived(ThisT* t)
+    {
+        return static_cast<DerivedT&>(*t);
+    }
+    template <class DerivedT, class ThisT>
+    const DerivedT&
+    derived(const ThisT* t)
+    {
+        return static_cast<const DerivedT&>(*t);
+    }
+
+
+    template <class DerivedT>
+    struct GetFindInstanceMixin
+    {
+        // Relies on this->find/getSnapshot()
+
+        bool hasInstance(const MemoryID& instanceID) const
+        {
+            return derived<DerivedT>(this).findInstance(instanceID) != nullptr;
+        }
+
+        /**
+         * @brief Find an entity instance.
+         * @param id The instance ID.
+         * @return The instance or nullptr if it is missing.
+         */
+        auto*
+        findInstance(const MemoryID& instanceID)
+        {
+            auto* snapshot = derived<DerivedT>(this).findSnapshot(instanceID);
+            return snapshot ? snapshot->findInstance(instanceID) : nullptr;
+        }
+        const auto*
+        findInstance(const MemoryID& instanceID) const
+        {
+            const auto* snapshot = derived<DerivedT>(this).findSnapshot(instanceID);
+            return snapshot ? snapshot->findInstance(instanceID) : nullptr;
+        }
+
+        /**
+         * @brief Retrieve an entity instance.
+         * @param id The instance ID.
+         * @return The instance if it is found.
+         * @throw `armem::error::ArMemError` if it is missing.
+         */
+        auto&
+        getInstance(const MemoryID& instanceID)
+        {
+            return derived<DerivedT>(this).getSnapshot(instanceID).getInstance(instanceID);
+        }
+        const auto&
+        getInstance(const MemoryID& instanceID) const
+        {
+            return derived<DerivedT>(this).getSnapshot(instanceID).getInstance(instanceID);
+        }
+    };
+
+
+    template <class DerivedT>
+    struct GetFindSnapshotMixin
+    {
+        // Relies on this->find/getEntity()
+
+        bool hasSnapshot(const MemoryID& snapshotID) const
+        {
+            return derived<DerivedT>(this).findSnapshot(snapshotID) != nullptr;
+        }
+
+        /**
+         * @brief Find an entity snapshot.
+         * @param id The snapshot ID.
+         * @return The snapshot or nullptr if it is missing.
+         */
+        auto*
+        findSnapshot(const MemoryID& snapshotID)
+        {
+            auto* entity = derived<DerivedT>(this).findEntity(snapshotID);
+            return entity ? entity->findSnapshot(snapshotID) : nullptr;
+        }
+        const auto*
+        findSnapshot(const MemoryID& snapshotID) const
+        {
+            auto* entity = derived<DerivedT>(this).findEntity(snapshotID);
+            return entity ? entity->findSnapshot(snapshotID) : nullptr;
+        }
+
+        /**
+         * @brief Retrieve an entity snapshot.
+         * @param id The snapshot ID.
+         * @return The snapshot if it is found.
+         * @throw `armem::error::ArMemError` if it is missing.
+         */
+        auto&
+        getSnapshot(const MemoryID& snapshotID)
+        {
+            return derived<DerivedT>(this).getEntity(snapshotID).getSnapshot(snapshotID);
+        }
+        const auto&
+        getSnapshot(const MemoryID& snapshotID) const
+        {
+            return derived<DerivedT>(this).getEntity(snapshotID).getSnapshot(snapshotID);
+        }
+    };
+
+
+
+    template <class DerivedT>
+    struct GetFindEntityMixin
+    {
+        // Relies on this->find/getProviderSegment()
+
+        bool hasEntity(const MemoryID& entityID) const
+        {
+            return derived<DerivedT>(this).findEntity(entityID) != nullptr;
+        }
+
+        /**
+         * @brief Find an entity.
+         * @param id The entity ID.
+         * @return The entity or nullptr if it is missing.
+         */
+        auto*
+        findEntity(const MemoryID& entityID)
+        {
+            auto* provSeg = derived<DerivedT>(this).findProviderSegment(entityID);
+            return provSeg ? provSeg->findEntity(entityID) : nullptr;
+        }
+        const auto*
+        findEntity(const MemoryID& entityID) const
+        {
+            auto* provSeg = derived<DerivedT>(this).findProviderSegment(entityID);
+            return provSeg ? provSeg->findEntity(entityID) : nullptr;
+        }
+
+        /**
+         * @brief Retrieve an entity.
+         * @param id The entity ID.
+         * @return The entity if it is found.
+         * @throw `armem::error::ArMemError` if it is missing.
+         */
+        auto&
+        getEntity(const MemoryID& entityID)
+        {
+            return derived<DerivedT>(this).getProviderSegment(entityID).getEntity(entityID);
+        }
+        const auto&
+        getEntity(const MemoryID& entityID) const
+        {
+            return derived<DerivedT>(this).getProviderSegment(entityID).getEntity(entityID);
+        }
+    };
+
+
+
+    template <class DerivedT>
+    struct GetFindProviderSegmentMixin
+    {
+        // Relies on this->find/getCoreSegment()
+
+        bool hasProviderSegment(const MemoryID& providerSegmentID) const
+        {
+            return derived<DerivedT>(this).findProviderSegment(providerSegmentID) != nullptr;
+        }
+
+        /**
+         * @brief Retrieve a provider segment.
+         * @param id The provider segment ID.
+         * @return The provider segment if it is found or nullptr if it missing.
+         */
+        auto*
+        findProviderSegment(const MemoryID& providerSegmentID)
+        {
+            auto* provSeg = derived<DerivedT>(this).findCoreSegment(providerSegmentID);
+            return provSeg ? provSeg->findProviderSegment(providerSegmentID) : nullptr;
+        }
+        const auto*
+        findProviderSegment(const MemoryID& providerSegmentID) const
+        {
+            auto* provSeg = derived<DerivedT>(this).findCoreSegment(providerSegmentID);
+            return provSeg ? provSeg->findProviderSegment(providerSegmentID) : nullptr;
+        }
+
+        /**
+         * @brief Retrieve a provider segment.
+         * @param id The provider segment ID.
+         * @return The provider segment if it is found.
+         * @throw `armem::error::ArMemError` if it is missing.
+         */
+        auto&
+        getProviderSegment(const MemoryID& providerSegmentID)
+        {
+            return derived<DerivedT>(this).getCoreSegment(providerSegmentID).getProviderSegment(providerSegmentID);
+        }
+        const auto&
+        getProviderSegment(const MemoryID& providerSegmentID) const
+        {
+            return derived<DerivedT>(this).getCoreSegment(providerSegmentID).getProviderSegment(providerSegmentID);
+        }
+
+    };
+
+}
-- 
GitLab