Skip to content
Snippets Groups Projects

Toy Event Store

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    The snippet can be accessed without any authentication.
    Authored by Gerhard Raven

    based on (some of the ideas behind) Louis Dionne's poly library...

    Edited
    snippetfile1.txt 7.87 KiB
    #include <typeinfo>
    #include <utility>
    #include <typeindex>
    #include <type_traits>
    #include <algorithm>
    #include <stdexcept>
    #include <optional>
    #include <iterator>
    
    void* magic_cast(std::type_index targetType, std::type_index sourceType, void*) { return nullptr; }
    
    struct VTable {
        void  (*delete_)(void*);
        void* (*cast_)(std::type_index, void*);
        std::optional<size_t> (*size_)(const void*);
        std::type_index (*type_)();
    };
    
    template <typename T>
    void delete_(void* self) { delete static_cast<T*>(self); }
    
    template <>
    void delete_<void>(void* self) { };
    
    template <typename T>
    std::type_index type_() { return std::type_index(typeid(T)); }
    
    template <typename T>
    void* cast_(std::type_index type, void* self) { 
        return type==std::type_index(typeid(T)) ? static_cast<T*>(self)
                                                : magic_cast(type,std::type_index(typeid(T)),self);
    }
    
    template <>
    void *cast_<void>(std::type_index,void*) { return nullptr; }
    
    namespace details {
      using std::size;
    
      template <typename T, typename... Args>
      constexpr auto size( const T&, Args&&... ) noexcept {
        static_assert( sizeof...( Args ) == 0, "No extra args please" );
        return std::nullopt;
      }
    }
    
    template <typename T>
    std::optional<size_t> size_(const void* self) { return details::size(*static_cast<const T*>(self)); }
    
    template <>
    std::optional<size_t> size_<void>(const void* ) { return std::nullopt; }
    
    template <typename T>
    inline constexpr VTable const vtable_for = { &delete_<T>, &cast_<T>, &size_<T>, &type_<T> };
    
    class Data {
        // for now, optimize for size. Could put selected functions also 'in-situ' to
        // improve optimization (eg. inlining) as 
        const VTable *m_vtable = &vtable_for<void>;
        void* m_ptr = nullptr;
        // TODO: add SBO here, so that eg. int, double, vector<T> payloads fit in-situ
        // WARNING: SBO will invalidate pointers to payload when resizing the store
        //          so _only_ do this _after_ we have a store which can be completely 
        //          reserved during initialize, _prior_ to it being populated with content!
        // (note: the above actually depends on the container implementation which is 
        //        used to contain 'Data'...  if the Data instances inside the container are
        //        stable then obviously SBO doesn't affect the payload stability ;-)
    public:
        Data() = default;
    
        template <typename T, // TODO: require T to be move constructible
                  typename = std::enable_if_t<!std::is_same_v<Data,std::decay_t<T>>>>
        Data(T&& t) : m_vtable{ &vtable_for<T> }, m_ptr{ new T{std::move(t)} } {
        }
    
        // 'adopt' a unique pointer...
        template <typename T>
        Data(std::unique_ptr<T,std::default_delete<T>> t) : m_vtable{ &vtable_for<T> }, m_ptr{ t.release() } {
        }
    
        ~Data() { m_vtable->delete_(m_ptr); }
    
        Data(const Data&) = delete;
        Data& operator=(const Data& rhs) = delete;
    
        Data(Data&& rhs) noexcept : m_vtable{ std::exchange(rhs.m_vtable,&vtable_for<void>) }, 
                                    m_ptr{ std::exchange(rhs.m_ptr,nullptr) } {
        }
        Data& operator=(Data&& rhs) noexcept {
            m_vtable->delete_(m_ptr);
            m_vtable = std::exchange(rhs.m_vtable,&vtable_for<void>);
            m_ptr = std::exchange(rhs.m_ptr,nullptr);
            return *this;
        }
    
        friend void swap(Data& lhs, Data& rhs) noexcept { 
            using std::swap;
            swap(lhs.m_vtable,rhs.m_vtable);
            swap(lhs.m_ptr,rhs.m_ptr);
        }
    
        template <typename T> const T* get_ptr() const {
            return static_cast<T*>(m_vtable->cast_(std::type_index(typeid(T)),m_ptr));
        }
    
        std::optional<size_t> size() const { return m_vtable->size_(m_ptr); }
    
        std::type_index type() const { return m_vtable->type_(); }
    };
    
    #include <vector>
    template <typename Key> 
    class VectorStore {
        std::vector<std::pair<Key,Data>> m_store;
    public:
        VectorStore() = default;
        
        template <typename K>
        const Data *find( K&& k ) const {
            auto i = std::find_if(m_store.begin(),m_store.end(),
                                  [key=std::forward<K>(k)](const auto& d) { return d.first==key; } );
            return i!=m_store.end() ? &i->second : nullptr;
        }
    
        template <typename T, typename K>
        const auto& emplace(K&& k, T&& t) {
            if (find(k)!=nullptr) throw std::runtime_error("entry already exists");
            return m_store.emplace_back(std::forward<K>(k), std::forward<T>(t) );
        }
    };
    
    #include <unordered_map>
    template <typename Key> 
    class UnorderedStore {
        std::unordered_map<Key,Data> m_store;
    public:
        UnorderedStore() = default;
        
        template <typename K>
        const Data *find( K&& k ) const {
            auto i = m_store.find( std::forward<K>(k) );
            return i!=m_store.end() ? &i->second : nullptr;
        }
    
        template <typename T, typename K>
        const auto& emplace(K&& k, T&& t) {
            if ( const auto&[ ret, ok ] = m_store.try_emplace(std::forward<K>(k), std::forward<T>(t) ); ok ) {
                return *ret;
            } 
            throw std::runtime_error("entry already exists");
        }
    };
    
    template <typename Key,template<typename>typename StoreImpl=UnorderedStore> 
    struct Store : private StoreImpl<Key> {
    
        template <typename T, typename K>
        const std::decay_t<T>& put( K&& k, T&& t ) { 
             auto& d = this->emplace(std::forward<K>(k),std::forward<T>(t));
             auto* p = d.second.template get_ptr<std::decay_t<T>>();
             return *p;
        }
    
        template <typename T, typename K>
        const std::decay_t<T>& put( K&& k, std::unique_ptr<T> t ) { 
             auto& d = this->emplace(std::forward<K>(k),std::move(t));
             auto* p = d.second.template get_ptr<std::decay_t<T>>();
             return *p;
        }
    
        template <typename T, typename K>
        const T& get(K&& k) const {
            const Data* d = this->find(std::forward<K>(k));
            if (!d) throw std::out_of_range{"oops"};
            const auto* p = d->get_ptr<T>();
            if (!p) throw std::bad_cast{};
            return *p;
        }
    
        template <typename T,typename K >
        const T* get_ptr(K&& k) const {
            const Data* d = this->find(std::forward<K>(k));
            if (!d) return nullptr;
            const auto* p = d->get_ptr<T>();
            if (!p) throw std::bad_cast{};
            return p;
        }
    
        template <typename K>
        std::optional<size_t> size(K&& k) const {
            const Data* d = this->find(std::forward<K>(k));
            if (!d) throw std::out_of_range{"oops"};
            return d->size();
        }
    
        template <typename K>
        const char* type_name(K&& k) const {
            const Data* d = this->find(std::forward<K>(k));
            if (!d) throw std::out_of_range{"oops"};
            return d->type().name();
        }
    };
    
    
    struct MyBase {
       virtual ~MyBase() = default;
       MyBase(int a) : i{a} {}
       int i;
    };
    struct Derived : MyBase {
       Derived(int a, int b) : MyBase{a},j{b} {}
       int j;
    };
    
    #include <string>
    #include <memory>
    auto make_store() {
        auto store = Store<std::string>{};
        store.put( "AVector", std::vector<int>{1,2,3,4,5,6,7,8,9} );
        [[maybe_unused]] const int& i = store.put( "AnInt", 99 );
        store.put( "ADouble", 123.45 );
        store.put( "AString", std::string{"Hello World!"} );
        store.put<std::string>( "AnotherString", "Goodbye World!" );
        [[maybe_unused]] const int& j = store.put( "AUniqueInt", std::make_unique<int>(-9) );
        store.put( "Derived", Derived{  1, 2  } );
        store.put<MyBase>( "Base", std::make_unique<Derived>( 3 , 4  ) );
        return store;
    }
    
    #include <iostream>
    int main() {
        auto s = make_store();
        std::cout << s.get<std::string>("AString") << '\n';
        std::cout << s.get<std::string>("AnotherString") << '\n';
        std::cout << s.get<int>("AUniqueInt") << '\n';
        std::cout << s.get<MyBase>("Base").i << '\n';
        std::cout << s.get<Derived>("Derived").j << '\n';
        std::cout << s.size("AString").value_or(0) <<  '\n';
        std::cout << s.size("AVector").value_or(0) <<  '\n';
        std::cout << s.size("AUniqueInt").value_or(0) <<  '\n';
        std::cout << s.type_name("AString") << '\n';
        std::cout << s.type_name("AUniqueInt") << '\n';
    }
    • Pretty neat!

      Beware that put must handle duplicate insertion.

      Also, cast's signature seems overkill if you aim for exact type equality. In this case, it can be simply bool(std::type_index). The void* signature is only needed if you want to keep the TES' ability to downcast multiple types into T*.

    • I was actually thinking of specializing cast_ to allow conversion to base... not sure if that can be done yet ;-) And yes, put is far too simple, but then again, this is a proof-of-principle working store in less than 100 lines of code...

    • @sponce, @clemenci -- FYI...

    • @rmatev, @nnolte -- FYI...

    • @raaij: FYI

    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment