Container für polymorphe Objekte gleicher Größe


  • Mod

    Beispiel

    #include <cassert>
    #include <new>
    #include <unordered_map>
    #include <utility>
    #include <typeinfo>
    #include <typeindex>
    
    struct copy_move_info_t {
        void* (*copy_construct)(void* dst, const void* src);
        void* (*move_construct)(void* dst, void* src);
        void* (*copy_assign)(void* dst, const void* src);
        void* (*move_assign)(void* dst, void* src);
        void (*swap)(void* x, void* y);
        bool is_nothrow_copy_constructible;
    };
    inline std::unordered_map<std::type_index, const copy_move_info_t*> cm_infos{};
    
    template <typename T>
    void* copy_construct(void* dst, const void* src) {
        return new(dst) T{*std::launder(static_cast<const T*>(src))};
    }
    template <typename T>
    void* move_construct(void* dst, void* src) {
       return new(dst) T{std::move(*std::launder(static_cast<T*>(src)))};
    }
    template <typename T>
    void* copy_assign(void* dst, const void* src) {
        *std::launder(static_cast<T*>(dst)) = *std::launder(static_cast<const T*>(src));
        return dst;
    }
    template <typename T>
    void* move_assign(void* dst, void* src) {
        *std::launder(static_cast<T*>(dst)) = std::move(*std::launder(static_cast<T*>(src)));
        return dst;
    }
    template <typename T>
    void swap_(void* x, void* y) {
        using std::swap;
        swap(*std::launder(static_cast<T*>(x)), *std::launder(static_cast<T*>(y)));
    }
    template <typename T>
    const copy_move_info_t copy_move_info{ copy_construct<T>, move_construct<T>, copy_assign<T>, move_assign<T>, swap_<T>, std::is_nothrow_copy_constructible_v<T> };
    
    template <typename T>
    class polymorph {
        static_assert(std::is_polymorphic_v<T>);
        static_assert(std::has_virtual_destructor_v<T>);
    public:
        template <typename U>
        polymorph(U&& src) noexcept(noexcept(std::decay_t<U>{std::forward<U>(src)})) {
            using src_type = std::decay_t<U>;
            static_assert(std::is_convertible_v<src_type*, T*>);
            static_assert(sizeof(src_type) == sizeof(T));
            static_assert(alignof(src_type) == alignof(T));
            static_assert(std::is_nothrow_move_constructible_v<src_type>);
            static_assert(std::is_nothrow_move_assignable_v<src_type>);
            static_assert(std::is_nothrow_destructible_v<src_type>);
            static_assert(std::is_nothrow_swappable_v<src_type>);
    
            cm_infos.try_emplace(typeid(U), &copy_move_info<src_type>);
            auto p = new(static_cast<void*>(data)) src_type{std::forward<U>(src)};
            assert(p == std::addressof(get()));
        }
        polymorph(const polymorph& other) {
            auto p = cm_infos.at(typeid(other.get()))->copy_construct(data, other.data);
            assert(p == std::addressof(get()));
        }
        polymorph(polymorph&& other) noexcept {
            auto p = cm_infos.at(typeid(other.get()))->move_construct(data, other.data);
            assert(p == std::addressof(get()));
        }
        polymorph& operator=(const polymorph& rhs) {
            auto&& info = cm_infos.at(typeid(rhs.get()));
            if (typeid(get()) == typeid(rhs.get()))
                info->copy_assign(data, rhs.data);
            else if (info->is_nothrow_copy_constructible) {
                get().~T();
                info->copy_construct(data, rhs.data);
            } else {
                alignas(T) char tmp[sizeof(T)];
                info->copy_construct(tmp, rhs.data);
                get().~T();
                auto p = info->move_construct(data, tmp);
                assert(p == std::addressof(get()));
                std::launder(reinterpret_cast<T*>(tmp))->~T();
            }
            return *this;
        }
        polymorph& operator=(polymorph&& rhs) noexcept {
            auto&& info = cm_infos.at(typeid(rhs.get()));
            if (typeid(get()) == typeid(rhs.get()))
                info->move_assign(data, rhs.data);
            else {
                get().~T();
                auto p = info->move_construct(data, rhs.data);
                assert(p == std::addressof(get()));
            }
            return *this;
        }
        ~polymorph() noexcept {
            get().~T();
        }
    
        void swap(polymorph& other) noexcept {
            auto&& info = cm_infos.at(typeid(get()));
            if (typeid(get()) == typeid(other.get()))
                info->swap(data, other.data);
            else {
                auto&& info_other = cm_infos.at(typeid(other.get()));
                alignas(T) char tmp[sizeof(T)];
                info_other->move_construct(tmp, other.data);
                other.get().~T();
                info->move_construct(other.data, data);
                get().~T();
                info_other->move_construct(data, tmp);
                std::launder(reinterpret_cast<T*>(tmp))->~T();
            }
        }
        void swap(polymorph&& other) noexcept {
            swap(other);
        }
    
        T& get() noexcept { return *std::launder(reinterpret_cast<T*>(data)); }
        const T& get() const noexcept { return *std::launder(reinterpret_cast<const T*>(data)); }
    private:
        alignas(T) char data[sizeof(T)];
    };
    template <typename T> void swap(polymorph<T>& x, polymorph<T>& y) noexcept { return x.swap(y); }
    template <typename T> void swap(polymorph<T>&& x, polymorph<T>& y) noexcept { return x.swap(y); }
    template <typename T> void swap(polymorph<T>& x, polymorph<T>&& y) noexcept { return x.swap(y); }
    template <typename T> void swap(polymorph<T>&& x, polymorph<T>&& y) noexcept { return x.swap(y); }
    ////////////////////////////////////////////////////////////////////////////////////////////
    #include <iostream>
    #include <vector>
    struct base {
        virtual void foo() = 0;
        virtual ~base() {}
    };
    template <int I>
    struct derived : base
    {
        derived() { std::cout << (void*)this << " derived<" << I << ">::derived()\n"; }
        derived(const derived&) { std::cout << (void*)this << " derived<" << I << ">::derived(const derived&)\n"; }
        derived(derived&&) noexcept { std::cout << (void*)this << " derived<" << I << ">::derived(derived&&)\n"; }
        derived& operator=(const derived&) { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(const derived&)\n"; return *this; }
        derived& operator=(derived&&) noexcept { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(derived&&)\n"; return *this; }
        ~derived() noexcept { std::cout << (void*)this << " derived<" << I << ">::~derived()\n"; }
        virtual void foo() override { std::cout << (void*)this << " derived<" << I << ">::foo()\n"; }
    };
    
    int main() {
        std::vector<polymorph<base>> v;
        std::cout << "v.emplace_back(derived<0>{});\n";
        v.emplace_back(derived<0>{});
        std::cout << "v.push_back(derived<1>{});\n";
        v.push_back(derived<1>{});
        std::cout << "v.emplace_back(derived<2>{});\n";
        v.emplace_back(derived<2>{});
        std::cout << "***\n";
        for ( auto& x : v )
            x.get().foo();
        std::cout << "v[0] = std::move(v[0]);\n";
        v[0] = std::move(v[0]);
        std::cout << "v[0] = std::move(v[2]);\n";
        v[0] = std::move(v[2]);
        std::cout << "v[0] = v[0];\n";
        v[0] = v[0];
        std::cout << "v[0] = v[1];\n";
        v[0] = v[1];
        std::cout << "swap(v[0],v[0]);\n";
        swap(v[0],v[0]);
        std::cout << "swap(v[0],v[2]);\n";
        swap(v[0],v[2]);
        std::cout << "return 0;\n";
        std::cout << "***\n";
        for ( auto& x : v )
            x.get().foo();
    }
    


  • Das hast du jetzt einfach in paar Minuten geschrieben? Hab mir schon gedacht, dass es so ähnlich ausschauen würde, aber wollte was fertiges, bevor ich das selber anfange 🙂 Aber das schaut gut aus, danke!

    - auf std::launder kann ich in C++14 einfach verzichten?
    - Gibts einen Grund, dass du kein aligned_storage verwendest (ich hätt sogar vorausgesetzt, dass die Typen gleich aligned sein müssen).


  • Mod

    PolyCollection schrieb:

    - auf std::launder kann ich in C++14 einfach verzichten?

    Im Prinzip ja. Besser wäre evtl. einfach ein eigenes launder-Template zu benutzen, dass das Funktionsargument zurückgibt. Dann musst du bei einer späteren Umstellung auf einen neuen Standard nicht den ganzen Code manuell durcharbeiten, sondern passt einfach das Template an oder führst ein automatische search&replace durch.

    PolyCollection schrieb:

    - Gibts einen Grund, dass du kein aligned_storage verwendest (ich hätt sogar vorausgesetzt, dass die Typen gleich aligned sein müssen).

    Dafür ist alignas da. aligned_storage existiert ja nur, weil es vor alignas/alignof eingeführt wurde.



  • Auch wenn es nur Beispielcode ist, globale Variablen, noch dazu mit nicht synchronisiertem Zugriff, finde ich schrecklich.


  • Mod

    hustbaer schrieb:

    Auch wenn es nur Beispielcode ist, globale Variablen, noch dazu mit nicht synchronisiertem Zugriff, finde ich schrecklich.

    Warum ist die Map eigentlich im namespace scope? AFAICS könnte das auch ein inline static member sein?

    PolyCollection schrieb:

    - auf std::launder kann ich in C++14 einfach verzichten?

    Das Objektmodell ist momentan kaputt*, und sobald es durch P0593 und Konsorten gefixt wird, ist der Code auch ohne launder gültig, weil das char Array bereits ein Objekt des zugehörigen Typen beinhaltet. Ergo, du kannst auf launder in diesem Kontext sowieso verzichten. launder wird dort gebraucht, wo wir bspw. ein neues Objekt in den Speicher eines alten setzen, und auf einen konstanten oder Referenzmember zugreifen. Kurz gesagt, aber natürlich nicht formell, wir möchten gewisse Datenfluss-Analysen, speziell pointer analysis, unterbinden.

    ^* Es ist sowohl praktisch kaputt (wir können keine Container implementieren), als auch bzgl. Abwärtskompatibilität. Und Code als undefiniert zu deklarieren, der in C++14 wohldefiniert war, ist einfach völlig lachhaft und wird nicht von Implementierungen berücksichtigt werden, bis der Staub sich legt.^


  • Mod

    Arcoth schrieb:

    hustbaer schrieb:

    Auch wenn es nur Beispielcode ist, globale Variablen, noch dazu mit nicht synchronisiertem Zugriff, finde ich schrecklich.

    Warum ist die Map eigentlich im namespace scope? AFAICS könnte das auch ein inline static member sein?

    Das ist ist im globalen Namensraum, damit sich jemand daran stört und den Code nicht einfach so ohne Nachdenken übernimmt.
    Synchronisation wurde angesprochen. Die ist dann und nur dann erforderlich, falls in mehreren Threads mit polymorph-Objekten gearbeitet wird und nicht alle Typen bereits zuvor registriert werden können.

    Und dann gibt es ja zwei offensichtliche Implementations-Alternativen, die bedacht werden sollten:
    1. man speichert einen entsprechenden Infozeiger mit jedem Objekt, Nachteil ist größerer Speicheraufwand, der bei großen Objekten durchaus irrelevant sein kann, oder
    2. man verlangt, dass Base ein entsprechendes virtuelles Interface besitzt, dass die nötigen Copy-/Move-operationen bereitstellt.


  • Mod

    Variante mit virtuellem Interface (kombiniert beide alterntive Varianten oben) und C++14 kompatibel. Diese Implementation muss mit diesen Beschränkungen zu leben:
    1. die Typen der gespeicherten Objekten dürfen nicht final sein (sowieso meistens Unfug),
    2. die gespeicherten Objekte sind Basisklassensubobjekte, Code der typeid verwendet muss angepasst werden.
    3. falls die Basisklasse nicht von holder_base abgeleitet ist, wird ein zusätzlicher Zeiger pro Objekt gebraucht (zusätzlicher vtbl-Pointer wg. Mehrfachvererbung) - zudem evtl. langsamer, wegen cross-cast.

    #include <cassert>
    #include <new>
    #include <utility>
    #include <tuple>
    #include <typeinfo>
    
    class holder_base {
    public:
        virtual void holder_copy_construct(void* dst) const { assert(false); };
        virtual void holder_move_construct(void* dst) noexcept { assert(false); };
        virtual void holder_copy_assign(holder_base& dst) const { assert(false); };
        virtual void holder_move_assign(holder_base& dst) noexcept { assert(false); };
        virtual void holder_swap(holder_base& other) noexcept { assert(false); };
        virtual ~holder_base() noexcept {};
    };
    
    template <typename T, typename U, std::enable_if_t<
           std::is_convertible<int std::decay_t<U>::*, int std::decay_t<T>::*>{}
        || std::is_convertible<std::remove_reference_t<U>*, std::remove_reference_t<T>*>{}, int> = 0>
    T fast_cast(U&& v) noexcept { return static_cast<T>(std::forward<U>(v)); }
    template <typename T, typename U, std::enable_if_t<
           !std::is_convertible<int std::decay_t<U>::*, int std::decay_t<T>::*>{}
        && !std::is_convertible<std::remove_reference_t<U>*, std::remove_reference_t<T>*>{}, int> = 0>
    T fast_cast(U&& v) noexcept { return dynamic_cast<T>(std::forward<U>(v)); }
    
    template <typename T, bool B = std::is_base_of<holder_base, T>{}>
    struct holder_helper : T {
        template <typename U>
        holder_helper(U&& v) : T{std::forward<U>(v)} {}
    };
    template <typename T>
    struct holder_helper<T, false> : holder_base, T {
        template <typename U>
        holder_helper(U&& v) : holder_base{}, T{std::forward<U>(v)} {}
    };
    
    template <typename T>
    class holder : public holder_helper<T> {
    public:
        template <typename U>
        explicit holder(U&& v) noexcept(noexcept(std::decay_t<U>{std::forward<U>(v)}))
        : holder_helper<T>{std::forward<U>(v)}
        {}
    private:
        virtual void holder_copy_construct(void* dst) const final override {
            new(dst) holder{static_cast<const T&>(*this)};
        }
        virtual void holder_move_construct(void* dst) noexcept final override {
            new(dst) holder{static_cast<T&&>(*this)};
        }
        template <typename U, std::enable_if_t<std::is_nothrow_copy_constructible<U>{}, int> = 0>
        static void holder_copy_assign(holder_base& dst, const U& src) noexcept {
            dst.~holder_base();
            src.holder_copy_construct(&dst);
        }
        template <typename U, std::enable_if_t<!std::is_nothrow_copy_constructible<U>{}, int> = 0>
        static void holder_copy_assign(holder_base& dst, const U& src) {
            T tmp{static_cast<const T&>(src)};
            dst.~holder_base();
            new(static_cast<void*>(&dst)) holder{std::move(tmp)};
        }
        virtual void holder_copy_assign(holder_base& dst) const final override {
            if (typeid(dst) == typeid(holder))
                static_cast<T&>(fast_cast<holder&>(dst)) = static_cast<const T&>(*this);
            else
                holder_copy_assign(dst, *this);
        }
        virtual void holder_move_assign(holder_base& dst) noexcept final override {
            if (typeid(dst) == typeid(holder))
                static_cast<T&>(fast_cast<holder&>(dst)) = static_cast<T&&>(*this);
            else {
                dst.~holder_base();
                holder_move_construct(&dst);
            }
        }
        virtual void holder_swap(holder_base& other) noexcept final override {
            if (typeid(other) == typeid(holder)) {
                using std::swap;
                swap(static_cast<T&>(fast_cast<holder&>(other)), static_cast<T&>(*this));
            } else {
                T tmp{static_cast<T&&>(*this)};
                this->~holder();
                other.holder_move_construct(this);
                other.~holder_base();
                new(static_cast<void*>(&other)) holder{std::move(tmp)};
            }
        }
    };
    
    template <typename T, std::size_t max_size = sizeof(holder<T>), std::size_t alignment = alignof(holder<T>)>
    class polymorph {
        static_assert(std::is_polymorphic<T>{}, "Base must be polymorphic");
        static_assert(std::has_virtual_destructor<T>{}, "Base must have virtual destructor");
        static_assert(max_size >= sizeof(holder<T>), "specified size is unsufficient");
        static_assert(alignment >= alignof(holder<T>), "specified alignment not strict enough");
        static_assert(max_size % alignment == 0, "specified size and alignment inconsistent");
    public:
        template <typename U>
        polymorph(U&& src) noexcept(noexcept(std::decay_t<U>{std::forward<U>(src)})) {
            using src_type = std::decay_t<U>;
            static_assert(std::is_convertible<src_type*, T*>{}, "Base must be unambigous public base of Argument");
            assert(typeid(src_type) == typeid(src)); // argument needs to be of the most derived type
            static_assert(!std::is_final<src_type>{}, "argument type must not be final");
            static_assert(sizeof(holder<src_type>) <= max_size, "argument size too big");
            static_assert(alignof(holder<src_type>) <= alignment, "argument alignment too strict");
            static_assert(std::is_nothrow_move_constructible<src_type>{}, "argument must be nothrow move constructible");
            static_assert(std::is_nothrow_move_assignable<src_type>{}, "argument must be nothrow move assignable");
            static_assert(std::is_nothrow_destructible<src_type>{}, "argument must be nothrow destructible");
            // static_assert(std::is_nothrow_swappable_v<src_type>);
    
            auto p = new(static_cast<void*>(data)) holder<src_type>{std::forward<U>(src)};
            assert(static_cast<holder_base*>(p) == static_cast<void*>(p)); // sane layout
        }
        polymorph(const polymorph& other)              { other.holder_get().holder_copy_construct(data); }
        polymorph(polymorph&& other) noexcept          { other.holder_get().holder_move_construct(data); }
        polymorph& operator=(const polymorph& rhs)     { rhs.holder_get().holder_copy_assign(holder_get()); return *this; }
        polymorph& operator=(polymorph&& rhs) noexcept { rhs.holder_get().holder_move_assign(holder_get()); return *this; }
        ~polymorph() noexcept                          { this->holder_get().~holder_base(); }
    
        void swap(polymorph& other) noexcept           { holder_get().holder_swap(other.holder_get()); }
        void swap(polymorph&& other) noexcept          { swap(other); }
    
        T& get() noexcept                              { return fast_cast<T&>(holder_get()); }
        const T& get() const noexcept                  { return fast_cast<const T&>(holder_get()); }
    private:
        holder_base& holder_get() noexcept             { return reinterpret_cast<holder_base&>(data); }
        const holder_base& holder_get() const noexcept { return reinterpret_cast<const holder_base&>(data); }
        alignas(alignment) char data[max_size];
    };
    
    template <typename T, std::size_t S, std::size_t A> void swap(polymorph<T, S, A>& x, polymorph<T, S, A>& y) noexcept { return x.swap(y); }
    template <typename T, std::size_t S, std::size_t A> void swap(polymorph<T, S, A>&& x, polymorph<T, S, A>& y) noexcept { return x.swap(y); }
    template <typename T, std::size_t S, std::size_t A> void swap(polymorph<T, S, A>& x, polymorph<T, S, A>&& y) noexcept { return x.swap(y); }
    template <typename T, std::size_t S, std::size_t A> void swap(polymorph<T, S, A>&& x, polymorph<T, S, A>&& y) noexcept { return x.swap(y); }
    ////////////////////////////////////////////////////////////////////////////////////////////
    #include <iostream>
    #include <vector>
    struct base : holder_base {
        virtual void foo() = 0;
        virtual ~base() {}
    };
    template <int I>
    struct derived : base
    {
        derived() { std::cout << (void*)this << " derived<" << I << ">::derived()\n"; }
        derived(const derived&) { std::cout << (void*)this << " derived<" << I << ">::derived(const derived&)\n"; }
        derived(derived&&) noexcept { std::cout << (void*)this << " derived<" << I << ">::derived(derived&&)\n"; }
        derived& operator=(const derived&) { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(const derived&)\n"; return *this; }
        derived& operator=(derived&&) noexcept { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(derived&&)\n"; return *this; }
        ~derived() noexcept { std::cout << (void*)this << " derived<" << I << ">::~derived()\n"; }
        virtual void foo() override { std::cout << (void*)this << " derived<" << I << ">::foo()\n"; }
    };
    
    int main() {
        std::vector<polymorph<base>> v;
        std::cout << "v.emplace_back(derived<0>{});\n";
        v.emplace_back(derived<0>{});
        std::cout << "v.push_back(derived<1>{});\n";
        v.push_back(derived<1>{});
        std::cout << "v.emplace_back(derived<2>{});\n";
        v.emplace_back(derived<2>{});
        std::cout << "***\n";
        for ( auto& x : v )
            x.get().foo();
        std::cout << "v[0] = std::move(v[0]);\n";
        v[0] = std::move(v[0]);
        std::cout << "v[0] = std::move(v[2]);\n";
        v[0] = std::move(v[2]);
        std::cout << "v[0] = v[0];\n";
        v[0] = v[0];
        std::cout << "v[0] = v[1];\n";
        v[0] = v[1];
        std::cout << "swap(v[0],v[0]);\n";
        swap(v[0],v[0]);
        std::cout << "swap(v[0],v[2]);\n";
        swap(v[0],v[2]);
        std::cout << "***\n";
        for ( auto& x : v )
            x.get().foo();
        std::cout << "return 0;\n";
    }
    


  • camper schrieb:

    Arcoth schrieb:

    hustbaer schrieb:

    Auch wenn es nur Beispielcode ist, globale Variablen, noch dazu mit nicht synchronisiertem Zugriff, finde ich schrecklich.

    Warum ist die Map eigentlich im namespace scope? AFAICS könnte das auch ein inline static member sein?

    Das ist ist im globalen Namensraum, damit sich jemand daran stört und den Code nicht einfach so ohne Nachdenken übernimmt.

    Ich bin nicht so optimistisch was das Mitdenken von Leuten die Code aus nem Forum bzw. allgemein aus einem Beispiel übernehmen angeht. Daher hab' ich es angesprochen. Hab schon zu oft erlebt dass mir ein Kollege auf die Frage wo Abscheuliches Konstrukt X herkommt die Antwort gibt "dass habe ich 1:1 von ... übernommen". Und dann auch noch meint dass das völlig OK wäre.



  • camper schrieb:

    1. die Typen der gespeicherten Objekten dürfen nicht final sein (sowieso meistens Unfug)

    Die Frage ist jetzt ziemlich OT, aber warum hältst du final für "meistens Unfug"?

    Ich finde final toll. Ich arbeite aktuell in einer grösseren Firma und du glaubst ja nicht auf was für Ideen Leute kommen. final ist dann erstmal ne gute Bremse. Denn die Hemmschwelle ein final wegzulöschen welches sich in Code befindet den nicht ihr Team "owned" ist schon eher gross. Bzw. ist es auch viel auffälliger bei Reviews wenn da auf einmal ein Edit in einem File ist wo bloss ein final weggelöscht wurde. Dann fragt man nach wieso, und kommt drauf dass jemand Unfug bauen wollte.

    Dass damit ein paar Tricks - wie den von dir verwendeten - verhindert, ist schade, aber mMn. das kleinere Übel.


  • Mod

    hustbaer schrieb:

    camper schrieb:

    1. die Typen der gespeicherten Objekten dürfen nicht final sein (sowieso meistens Unfug)

    Die Frage ist jetzt ziemlich OT, aber warum hältst du final für "meistens Unfug"?

    Ich finde final toll. Ich arbeite aktuell in einer grösseren Firma und du glaubst ja nicht auf was für Ideen Leute kommen. final ist dann erstmal ne gute Bremse. Denn die Hemmschwelle ein final wegzulöschen welches sich in Code befindet den nicht ihr Team "owned" ist schon eher gross. Bzw. ist es auch viel auffälliger bei Reviews wenn da auf einmal ein Edit in einem File ist wo bloss ein final weggelöscht wurde. Dann fragt man nach wieso, und kommt drauf dass jemand Unfug bauen wollte.

    Dass damit ein paar Tricks - wie den von dir verwendeten - verhindert, ist schade, aber mMn. das kleinere Übel.

    Das ist dann Social engineering und davon habe ich keine Ahnung - deshalb: "meistens". Wenn ich hier sage, final sei Unfug meine ich, dass es weder hilft, das Programm zu verstehen, noch für bessere Codegenerierung sorgt. Gleichzeitig werden bestimmte Implementierungstechniken verhindert.

    Statt:
    holder_base
    |
    base
    |
    derived
    |
    holder<derived>

    könnte eine Implentierung auch CRTP benutzen:
    base
    |
    copymove_base<base>
    |
    copymove<derived, base>
    |
    derived

    das funktioniert dann auch mit final Klassen und das typeid-Problem besteht nicht. Im Gegenzug muss jede einzelne abgeleitete Klasse geändert werden, statt nur die Basis einmal. Wenn man allerdings die Basis sowieso nicht ändern darf, ist das auch kein Nachteil.


  • Mod

    camper schrieb:

    Wenn ich hier sage, final sei Unfug meine ich, dass es weder hilft, das Programm zu verstehen, noch für bessere Codegenerierung sorgt. Gleichzeitig werden bestimmte Implementierungstechniken verhindert.

    Devirtualization?



  • camper schrieb:

    Das ist ist im globalen Namensraum, damit sich jemand daran stört und den Code nicht einfach so ohne Nachdenken übernimmt.

    Keine Sorge, ich hatte das nicht übernommen. Ich hab mich hauptsächlich grob daran orientiert und habs dann noch etwas anders gemacht.



  • camper schrieb:

    Wenn ich hier sage, final sei Unfug meine ich, dass es weder hilft, das Programm zu verstehen, noch für bessere Codegenerierung sorgt.

    Hm. Ich finde schon dass es hilft das Programm zu verstehen. Vielleicht nicht mega viel, aber ich meine es hilft mir schon zu wissen dass eine Klasse oder Funktion die ich mir gerade angucke maximal in dieser "most derived" Form verwendet wird. Speziell wenn das Ding virtuelle Funktionen hat muss ich mir ab da keinen Kopf mehr machen ob die nochmal wo überschrieben werden.



  • Etwas mehr zum Thema:

    Ich hätte mir schon ein paar mal gewünscht sowas in der Art zu haben. Allerdings nicht beschränkt auf die Grösse der Basisklasse, sondern mit einer Maximalgrösse die man als Template-Argument angeben kann. Evtl. auch mit der Möglichkeit grössere Objekte dann auf dem Heap zu erzeugen - wobei dieser Teil maximal "nice to have" wäre.

    Gibt es schon Proposals für eine Klasse dieser Art? Oder etwas in der Boost?

    Und fändet ihr sowas praktisch oder ist das etwas was eurer Meinung nach so selten gebraucht/gefragt wird dass es keinen Sinn macht?

    EDIT: Natürlich wäre, wenn man sowas in Boost/dem Standard aufnehmen wollen würde, eine Variante sinnvoll die mit beliebigen Typen funktioniert. Mein Favorit wäre da die "zusätzlicher Zeiger" Variante, weil schnell, flexibel und mMn. vertretbarer Speicher-Overhead.


  • Mod

    Also eine Art std::any mit SOO (small object optimization) ?

    Edit: std::any tut das bereits, nur kann man den für kleine Objekte verfügbaren Speicher nicht konfigurieren.



  • Ein std::any mit (idealerweise konfigurierbarer) SOO und der Möglichkeit einen Basisklassenzeiger zu bekommen (soweit ich weiss kann man aus std::any nur exakt das rausholen was man reingesteckt hat, aber eben nicht nen Zeiger/ne Referenz auf ein Basisklassensubobjekt).

    Also quasi ein small_polymorphic_any .


  • Mod

    Das dürfte ohne zusätzlichen Compilersupport unmöglich sein.
    Man müsste quasi einen dynamic_cast haben, der auf type_info-Objekten anstatt statisch bekannten Typen beruht.


  • Mod

    Man könnte möglicherweise etwas mit exception_ptr basteln.
    D sei der gespeicherter Typ. Wenn my_any z.B. zusätzlich einen exception_ptr der eine D* Exception hält, speichert, dann wird beim Zugriff via Typ B einfach die Exception wieder geworden und per B* oder void* - Handler gefangen.
    ungefähr so:

    struct my_any {
        template <typename T>
        my_any(T&& v) {
            // store data
            ...
            stored_exception = std::make_exception_ptr(static_cast<std::decay_t<T>*>(get()));
        }
    ...
        void* get(); // raw data
    ...
        template <typename T>
        T& any_cast() {
            try {
                std::rethrow_exception(stored_exception);
            } catch (T* p) {
                return *p;
            } catch (void* p) {
                throw bad_cast();
            }
        }
    ...
        std::exception_ptr stored_exception;
    };
    

  • Mod

    POC:

    #include <any>
    #include <cassert>
    #include <cstddef>
    #include <exception>
    #include <new>
    #include <unordered_map>
    #include <utility>
    #include <typeinfo>
    
    template <typename T> std::exception_ptr copy_construct(void* dst, const void* src) {
        return std::make_exception_ptr(new(dst) T{*static_cast<const T*>(src)});
    }
    template <typename T> std::pair<std::exception_ptr, void*> copy_construct(const void* src) {
        auto p = new T{*static_cast<const T*>(src)};
        return { std::make_exception_ptr(p), p };
    }
    template <typename T> std::exception_ptr move_construct(void* dst, void* src) {
        return std::make_exception_ptr(new(dst) T{std::move(*static_cast<T*>(src))});
    }
    template <typename T> std::pair<std::exception_ptr, void*> move_construct(void* src) {
        auto p = new T{std::move(*static_cast<T*>(src))};
        return { std::make_exception_ptr(p), p };
    }
    template <typename T> void copy_assign(void* dst, const void* src) {
        *static_cast<T*>(dst) = *static_cast<const T*>(src);
    }
    template <typename T> void move_assign(void* dst, void* src) {
        *static_cast<T*>(dst) = std::move(*static_cast<T*>(src));
    }
    template <typename T> void destroy(void* dst) {
        static_cast<T*>(dst)->~T();
    }
    template <typename T> void swap_(void* x, void* y) {
        using std::swap;
        swap(*static_cast<T*>(x), *static_cast<T*>(y));
    }
    
    struct copy_move_info_t {
        std::exception_ptr (*copy_construct)(void* dst, const void* src);
        std::exception_ptr (*move_construct)(void* dst, void* src);
        std::pair<std::exception_ptr, void*> (*copy_construct_)(const void* src);
        std::pair<std::exception_ptr, void*> (*move_construct_)(void* src);
        void (*copy_assign)(void* dst, const void* src);
        void (*move_assign)(void* dst, void* src);
        void (*destroy)(void* dst);
        void (*swap)(void* x, void* y);
        bool is_nothrow_copy_constructible;
        bool is_nothrow_move_constructible;
        bool is_nothrow_copy_assignable;
        bool is_nothrow_move_assignable;
        bool is_nothrow_destructible;
        bool is_nothrow_swappable;
        std::size_t size;
        std::size_t alignment;
        const std::type_info* info;
    };
    
    template <typename T>
    inline constexpr copy_move_info_t copy_move_info = {
        copy_construct<T>, move_construct<T>, copy_construct<T>, move_construct<T>, copy_assign<T>, move_assign<T>, destroy<T>, swap_<T>,
        std::is_nothrow_copy_constructible_v<T>, std::is_nothrow_move_constructible_v<T>,
        std::is_nothrow_copy_assignable_v<T>, std::is_nothrow_move_assignable_v<T>,
        std::is_nothrow_destructible_v<T>, std::is_nothrow_swappable_v<T>,
        sizeof(T), alignof(T), &typeid(T)
    };
    
    template <std::size_t internal_size = 4 * sizeof(std::max_align_t), std::size_t alignment = alignof(std::max_align_t)>
    class anymorph {
        static_assert(alignment != 0 && (alignment & (alignment - 1)) == 0 && internal_size % alignment == 0);
    public:
        template <typename U>
        anymorph(U&& src) noexcept(noexcept(std::decay_t<U>{std::forward<U>(src)})) {
            using src_type = std::decay_t<U>;
            src_type* p;
            if constexpr (alignof(src_type) <= alignment && sizeof(src_type) <= internal_size) {
                p = new(static_cast<void*>(data)) src_type(std::forward<U>(src));
                internal = true;
            } else {
                data_ptr = p = new src_type(std::forward<U>(src));
                internal = false;
            }
            cp_info = &copy_move_info<src_type>;
            stored_exception = std::make_exception_ptr(p);
        }
        anymorph(const anymorph& other) {
            if (other.cp_info->size <= internal_size && other.cp_info->alignment <= alignment) {
                stored_exception = other.cp_info->copy_construct(data, other.get_ptr());
                internal = true;
            } else {
                std::tie(stored_exception, data_ptr) = other.cp_info->copy_construct_(other.get_ptr());
                internal = false;
            }
            cp_info = other.cp_info;
        }
        anymorph(anymorph&& other) {
            if (other.cp_info->size <= internal_size && other.cp_info->alignment <= alignment) {
                stored_exception = other.cp_info->move_construct(data, other.get_ptr());
                internal = true;
            } else {
                std::tie(stored_exception, data_ptr) = other.cp_info->move_construct_(other.get_ptr());
                internal = false;
            }
            cp_info = other.cp_info;
        }
        anymorph& operator=(const anymorph& rhs) {
            if (cp_info == rhs.cp_info)
                cp_info->copy_assign(get_ptr(), rhs.get_ptr());
            else if (rhs.cp_info->size > internal_size || rhs.cp_info->alignment > alignment
                || (internal && !(cp_info->is_nothrow_destructible && rhs.cp_info->is_nothrow_copy_constructible))) {
                auto [except, ptr] = rhs.cp_info->copy_construct_(rhs.get_ptr());
                try {
                    cp_info->destroy(get_ptr());
                    data_ptr = ptr;
                    internal = false;
                    cp_info = rhs.cp_info;
                    stored_exception = except;
                } catch (...) {
                    data_ptr = ptr;
                    internal = false;
                    cp_info = rhs.cp_info;
                    stored_exception = except;
                    throw;
                }
            } else if (!internal) {
                void* p = data_ptr;
                try {
                    stored_exception = rhs.cp_info->copy_construct(data, rhs.get_ptr());
                } catch (...) {
                    data_ptr = p;
                    throw;
                }
                internal = true;
                auto old_cp_info = cp_info;
                cp_info = rhs.cp_info;
                old_cp_info->destroy(p);
            } else { // internal && cp_info->is_nothrow_destructible && rhs.cp_info->is_nothrow_copy_constructible
                cp_info->destroy(data);
                internal = true;
                cp_info = rhs.cp_info;
                stored_exception = cp_info->copy_construct(data, rhs.get_ptr());
            }
            return *this;
        }
        anymorph& operator=(anymorph&& rhs) {
            if (cp_info == rhs.cp_info)
                cp_info->move_assign(get_ptr(), rhs.get_ptr());
            else if (rhs.cp_info->size > internal_size || rhs.cp_info->alignment > alignment
                || (internal && !(cp_info->is_nothrow_destructible && rhs.cp_info->is_nothrow_move_constructible))) {
                auto [except, ptr] = rhs.cp_info->move_construct_(rhs.get_ptr());
                try {
                    cp_info->destroy(get_ptr());
                    data_ptr = ptr;
                    internal = false;
                    cp_info = rhs.cp_info;
                    stored_exception = except;
                } catch (...) {
                    data_ptr = ptr;
                    internal = false;
                    cp_info = rhs.cp_info;
                    stored_exception = except;
                    throw;
                }
            } else if (!internal) {
                void* p = data_ptr;
                try {
                    stored_exception = rhs.cp_info->move_construct(data, rhs.get_ptr());
                } catch (...) {
                    data_ptr = p;
                    throw;
                }
                internal = true;
                auto old_cp_info = cp_info;
                cp_info = rhs.cp_info;
                old_cp_info->destroy(p);
            } else { // internal && cp_info->is_nothrow_destructible && rhs.cp_info->is_nothrow_move_constructible
                cp_info->destroy(data);
                internal = true;
                cp_info = rhs.cp_info;
                stored_exception = cp_info->move_construct(data, rhs.get_ptr());
            }
            return *this;
        }
        ~anymorph() {
            cp_info->destroy(get_ptr());
        }
    
        void swap(anymorph& other) {
            using std::swap;
            if (this == &other) {
            } else if (!internal && !other.internal) {
                swap(data_ptr, other.data_ptr);
                swap(cp_info, other.cp_info);
                swap(stored_exception, other.stored_exception);
            } else if (internal && other.internal && cp_info == other.cp_info) {
                cp_info->swap(data, other.data);
                swap(stored_exception, other.stored_exception);
            } else {
                auto [except1, ptr1] = cp_info->copy_construct_(get_ptr());
                try {
                    auto [except2, ptr2] = other.cp_info->copy_construct_(other.get_ptr());
                    try {
                        cp_info->destroy(get_ptr());
                        try {
                            other.cp_info->destroy(other.get_ptr());
                            data_ptr = ptr2;
                            internal = false;
                            stored_exception = except2;
                            other.data_ptr = ptr1;
                            other.internal = false;
                            other.stored_exception = except1;
                            swap(cp_info, other.cp_info);
                        } catch (...) {
                            other.data_ptr = ptr2;
                            other.internal = false;
                            other.stored_exception = except2;
                            ptr2 = nullptr;
                            throw;
                        }
                    } catch (...) {
                        data_ptr = ptr1;
                        internal = false;
                        stored_exception = except1;
                        ptr1 = nullptr;
                        if (ptr2)
                            other.cp_info->destroy(ptr2);
                        throw;
                    }
                } catch (...) {
                    if (ptr1)
                        cp_info->destroy(ptr1);
                    throw;
                }
            }
        }
        void swap(anymorph&& other) {
            swap(other);
        }
    
        const std::type_info& type() const noexcept { return *cp_info->info; }
    
        template <typename T, std::size_t S, std::size_t A>
        friend T any_cast(anymorph<S, A>& v);
    private:
        void* get_ptr() noexcept { return internal ? data : data_ptr; }
        const void* get_ptr() const noexcept { return internal ? data : data_ptr; }
        union {
            alignas(alignment) char data[internal_size];
            void* data_ptr;
        };
        const copy_move_info_t* cp_info;
        bool internal;
        std::exception_ptr stored_exception;
    
        friend void swap(anymorph& x, anymorph& y) { return x.swap(y); }
        friend void swap(anymorph& x, anymorph&& y) { return x.swap(y); }
        friend void swap(anymorph&& x, anymorph& y) { return x.swap(y); }
        friend void swap(anymorph&& x, anymorph&& y) { return x.swap(y); }
    };
    template <typename T, std::size_t S, std::size_t A>
    T any_cast(anymorph<S, A>& v) {
        using U = std::decay_t<T>;
        try {
            std::rethrow_exception(v.stored_exception);
        } catch (U* p) {
            return *p;
        } catch (void*) {
            throw std::bad_any_cast();
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////////////////////
    #include <iostream>
    #include <vector>
    struct base {
        base(int i) : value(i) {}
        void foo() { std::cout << "base::foo() " << value << '\n'; }
        int value;
    };
    template <int I>
    struct derived : base
    {
        derived() : base(I) { std::cout << (void*)this << " derived<" << I << ">::derived()\n"; }
        derived(const derived&) : base(I) { std::cout << (void*)this << " derived<" << I << ">::derived(const derived&)\n"; }
        derived(derived&&) noexcept : base(I) { std::cout << (void*)this << " derived<" << I << ">::derived(derived&&)\n"; }
        derived& operator=(const derived&) { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(const derived&)\n"; return *this; }
        derived& operator=(derived&&) noexcept { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(derived&&)\n"; return *this; }
        ~derived() noexcept { std::cout << (void*)this << " derived<" << I << ">::~derived()\n"; }
        void foo() { std::cout << (void*)this << " derived<" << I << ">::foo()\n"; }
    };
    
    int main() {
        try {
            std::vector<anymorph<>> v;
            std::cout << "v.emplace_back(derived<0>{});\n";
            v.emplace_back(derived<0>{});
            std::cout << "v.push_back(derived<1>{});\n";
            v.push_back(derived<1>{});
            std::cout << "v.emplace_back(derived<2>{});\n";
            v.emplace_back(derived<2>{});
            std::cout << "***\n";
            for ( auto& x : v )
                any_cast<base&>(x).foo();
            std::cout << "v[0] = std::move(v[0]);\n";
            v[0] = std::move(v[0]);
            std::cout << "v[0] = std::move(v[2]);\n";
            v[0] = std::move(v[2]);
            std::cout << "v[0] = v[0];\n";
            v[0] = v[0];
            std::cout << "v[0] = v[1];\n";
            v[0] = v[1];
            std::cout << "swap(v[0],v[0]);\n";
            swap(v[0],v[0]);
            std::cout << "swap(v[0],v[2]);\n";
            swap(v[0],v[2]);
            std::cout << "return 0;\n";
            std::cout << "***\n";
            for ( auto& x : v )
                any_cast<base&>(x).foo();
        } catch (...) {
        }
    }
    

    base muss hier nicht einmal polymorph sein.
    Ohne gemessen zu haben, gehe ich aber davon aus, dass das Vorteil von SOO durch den exception_ptr mehr als verloren geht.


  • Mod

    Optimierte Version (weniger Speicherverbrauch, weniger virtual-Dispatch), die als drop-in Ersatz für std::any dienen kann.
    any_cast verhält sich wie std::any_cast (und dürfte genauso schnell sein), anymorph_cast erlaubt casts zu eindeutigen und öffentlichen Basisklassen.
    Um nicht für jedes einzelne Objekt einen exception_ptr zu benötigen, erzeugen ich diesen nur einmal pro Typ.
    Der Zeiger zeigt dabei auf eine memcpy-Kopie des ersten so gespeicherten Objektes. Das ist zwar undefiniertes Verhalten, dürfte aber mit allen gängigen Compilern funktionieren.
    Demo
    scope_guard.hpp

    #pragma once
    
    // adapted from A. Alexandrescu's ScopeGuard (CppCon 2015)
    
    namespace scope_guard {
        class UncaughtExceptionCounter {
            int exceptionCount_;
        public:
            UncaughtExceptionCounter() noexcept
            : exceptionCount_(std::uncaught_exceptions())
            {}
            bool newUncaughtException() noexcept {
                return std::uncaught_exceptions() > exceptionCount_;
            }
        };
    
        template <class Fun>
        class ScopeGuard {
            Fun f_;
        public:
            ScopeGuard() = delete;
            ScopeGuard(const Fun& f) noexcept : f_(f) {}
            ScopeGuard(Fun&& f)      noexcept : f_(std::move(f)) {}
            ScopeGuard(const ScopeGuard&)            = delete;
            ScopeGuard(ScopeGuard&& rhs) noexcept    = delete;
            ScopeGuard& operator=(const ScopeGuard&) = delete;
            ScopeGuard& operator=(ScopeGuard&&)      = delete;
            ~ScopeGuard() { f_(); }
        };
    
    	enum class ScopeGuardOnExit {};
    
    	template <typename Fun>
    	auto operator+(ScopeGuardOnExit, Fun&& fn) noexcept {
            return ScopeGuard<Fun>(std::forward<Fun>(fn));
    	}
    
        template <typename Fun, bool executeOnException>
        class ScopeGuardForNewException {
            Fun f_;
            UncaughtExceptionCounter ec_;
        public:
            explicit ScopeGuardForNewException(const Fun& fn) noexcept : f_(fn), ec_() {}
            explicit ScopeGuardForNewException(Fun&& fn)      noexcept : f_(std::move(fn)), ec_() {}
            ScopeGuardForNewException(const ScopeGuardForNewException&)            = delete;
            ScopeGuardForNewException(ScopeGuardForNewException&&)                 = default;
            ScopeGuardForNewException& operator=(const ScopeGuardForNewException&) = delete;
            ScopeGuardForNewException& operator=(ScopeGuardForNewException&&)      = delete;
            ~ScopeGuardForNewException() noexcept(!executeOnException && noexcept(f_())) {
                if (executeOnException == ec_.newUncaughtException()) {
                    f_();
                }
            }
        };
    
        enum class ScopeGuardOnFail {};
    
        template <typename FunctionType>
        auto operator+(ScopeGuardOnFail, FunctionType&& fn) noexcept {
            return ScopeGuardForNewException<std::decay_t<FunctionType>, true>(std::forward<FunctionType>(fn));
        }
    
        enum class ScopeGuardOnSuccess {};
    
        template <typename FunctionType>
        auto operator+(ScopeGuardOnSuccess, FunctionType&& fn) noexcept {
            return ScopeGuardForNewException<std::decay_t<FunctionType>, false>(std::forward<FunctionType>(fn));
        }
    }
    
    #define SCOPE_GUARD_CONCATENATE_IMPL(s1, s2) s1##s2
    #define SCOPE_GUARD_CONCATENATE(s1, s2) SCOPE_GUARD_CONCATENATE_IMPL(s1, s2)
    #ifdef __COUNTER__
    #define SCOPE_GUARD_ANONYMOUS_VARIABLE(str) SCOPE_GUARD_CONCATENATE(str, __COUNTER__)
    #else
    #define SCOPE_GUARD_ANONYMOUS_VARIABLE(str) SCOPE_GUARD_CONCATENATE(str, __LINE__)
    #endif
    
    #define SCOPE_EXIT \
        auto SCOPE_GUARD_ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) = ::scope_guard::ScopeGuardOnExit() + [&]()
    #define SCOPE_FAIL \
        auto SCOPE_GUARD_ANONYMOUS_VARIABLE(SCOPE_FAIL_STATE) = ::scope_guard::ScopeGuardOnFail() + [&]() noexcept
    #define SCOPE_SUCCESS \
        auto SCOPE_GUARD_ANONYMOUS_VARIABLE(SCOPE_SUCCESS_STATE) = ::scope_guard::ScopeGuardOnSuccess() + [&]()
    

    anymorph.hpp

    #pragma once
    
    #include <any>
    #include <array>
    #include <atomic>
    #include <cassert>
    #include <cstddef>
    #include <cstring>
    #include <exception>
    #include <initializer_list>
    #include <mutex>
    #include <new>
    #include <utility>
    #include <type_traits>
    #include <typeinfo>
    #include "scope_guard.hpp"
    
    #define LINE_(x) #x
    #define LINE LINE_(__LINE__)
    
    class bad_anymorph_cast : public std::bad_cast {
    public:
        const char* what() const noexcept override { return "bad anymorph_cast"; }
    };
    
    namespace anymorph_detail {
    
        template <std::size_t size, std::size_t alignment>
        alignas(alignment) char aligned_data[size];
    
        template <typename T>
        using has_stateless_zombie = std::bool_constant<std::is_trivial_v<T> || std::is_standard_layout_v<T>>;
        template <typename T>
        constexpr bool has_stateless_zombie_v = has_stateless_zombie<T>::value;
    
        std::mutex zombie_storage_mutex;
    
        template <typename T, bool = has_stateless_zombie_v<T>>
        struct zombie_storage;
    
        template <typename T>
        struct zombie_storage<T, true> {
            static const std::exception_ptr zombie_ptr;
            static constexpr const char* zombie = aligned_data<sizeof(T), alignof(T)>;
            static void initialize(T*) noexcept {}
        };
    
        template <typename T>
        const std::exception_ptr zombie_storage<T, true>::zombie_ptr =
            std::make_exception_ptr(reinterpret_cast<T*>(aligned_data<sizeof(T), alignof(T)>));
    
        template <typename T>
        struct zombie_storage<T, false> {
            static thread_local bool have_zombie;
            static std::atomic<bool> atomic_have_zombie;
            static char zombie[sizeof(T)];
            static std::exception_ptr zombie_ptr;
            static void initialize(T* p) noexcept {
                if (have_zombie)
                    return;
                if (atomic_have_zombie.load(std::memory_order_acquire)) {
                    std::atomic_thread_fence(std::memory_order_acquire);
                    have_zombie = true;
                    return;
                }
                std::scoped_lock guard(zombie_storage_mutex);
                if (atomic_have_zombie.load(std::memory_order_acquire)) {
                    std::atomic_thread_fence(std::memory_order_acquire);
                    have_zombie = true;
                    return;
                }
                // std::launder in order to silence spurious -Wmaybe-uninitialized warnings with gcc8
                std::memcpy(zombie, std::launder(p), sizeof(*p));
                zombie_ptr = std::make_exception_ptr(reinterpret_cast<T*>(zombie));
                atomic_have_zombie.store(true, std::memory_order_release);
                std::atomic_thread_fence(std::memory_order_release);
                have_zombie = true;
            }
        };
    
        template <typename T>
        thread_local bool zombie_storage<T, false>::have_zombie = false;
        template <typename T>
        std::atomic<bool> zombie_storage<T, false>::atomic_have_zombie = false;
        template <typename T>
        alignas(T) char zombie_storage<T, false>::zombie[sizeof(T)];
        template <typename T>
        std::exception_ptr zombie_storage<T, false>::zombie_ptr;
    
        struct empty_anymorph_value {};
    }
    
    template <std::size_t soo_size = sizeof(void*), std::size_t soo_align = alignof(void*)>
    class anymorph {
        static_assert(soo_align != 0 && (soo_align & (soo_align - 1)) == 0 && soo_size % soo_align == 0);
    
        template <typename T>
        using is_soo_type = std::bool_constant<sizeof(T) <= soo_size && alignof(T) <= soo_align
                                               && std::is_nothrow_move_constructible_v<T>>;
        template <typename T>
        static constexpr bool is_soo_type_v = is_soo_type<T>::value;
    
        using soo_storage = std::array<char, soo_size>;
    
        struct delegate_t {};
    
        struct mem_manager_base;
    
        struct storage {
            union {
                alignas(soo_align) soo_storage data;
                void* ptr;
            };
            const mem_manager_base* manager;
            void* get_ptr() noexcept { return is_internal() ? &data[0] : ptr; }
            const void* get_ptr() const noexcept { return is_internal() ? &data[0] : ptr; }
            bool is_internal() const noexcept { return manager->is_internal; }
        };
    
        struct mem_manager_base {
            constexpr mem_manager_base(bool internal, bool triv_destruct, bool trivial,
                                       const std::exception_ptr& zombie_ptr, const char* zombie_data, const std::type_info& type) noexcept
            : is_internal(internal), is_trivially_destructible(triv_destruct),
              is_trivially_deletable(internal && triv_destruct), is_trivial(trivial),
              is_internal_trivial(internal && trivial),
              zombie_ptr(zombie_ptr), zombie_data(zombie_data), type(type)
            {}
            constexpr mem_manager_base(const mem_manager_base&) = default;
            constexpr mem_manager_base(mem_manager_base&&) = default;
            mem_manager_base& operator=(const mem_manager_base&) = default;
            mem_manager_base& operator=(mem_manager_base&&) = default;
            virtual ~mem_manager_base() noexcept = default;
            virtual void  copy_construct(storage& dst, const storage& src) const = 0;
            virtual void  move_construct(storage& dst, storage& src) const = 0;
            virtual void  move_construct(soo_storage& dst, soo_storage& src) const noexcept = 0;
            virtual void  move_construct_and_destroy(soo_storage& dst, soo_storage& src) const noexcept = 0;
            virtual void  copy_assign(storage& dst, const storage& src) const = 0;
            virtual void  move_assign(storage& dst, storage& src) const = 0;
            virtual void  swap(storage& this_, storage& other) const noexcept = 0;
            virtual void  destroy(storage& dst) const noexcept = 0;
            virtual void  delete_(storage& dst) const noexcept = 0;
            virtual void  delete_(void* p) const noexcept = 0;
            bool is_internal;
            bool is_trivially_destructible;
            bool is_trivially_deletable;
            bool is_trivial;
            bool is_internal_trivial;
            const std::exception_ptr& zombie_ptr;
            const char* zombie_data;
            const std::type_info& type;
        };
        template <typename T, bool = is_soo_type_v<T>>
        struct mem_manager;
        template <typename T>
        static mem_manager<T> manager;
    
        template <typename T>
        struct mem_manager<T, true> : mem_manager_base {
            mem_manager() noexcept
            : mem_manager_base(true, std::is_trivially_destructible_v<T>, std::is_trivial_v<T>,
                               anymorph_detail::zombie_storage<T>::zombie_ptr,
                               anymorph_detail::zombie_storage<T>::zombie, typeid(T))
            {}
            virtual void copy_construct(storage& dst, const storage& src) const final override {
                assert(src.manager == this);
                copy_construct(dst.data, src.data);
                dst.manager = this;
            }
            void copy_construct(soo_storage& dst, const soo_storage& src) const {
                new(static_cast<void*>(&dst[0])) T(reinterpret_cast<const T&>(src[0]));
            }
            virtual void move_construct(storage& dst, storage& src) const final override {
                assert(src.manager == this);
                move_construct(dst.data, src.data);
                dst.manager = this;
            }
            virtual void move_construct(soo_storage& dst, soo_storage& src) const noexcept final override {
                new(static_cast<void*>(&dst[0])) T(reinterpret_cast<T&&>(src[0]));
            }
            virtual void move_construct_and_destroy(soo_storage& dst, soo_storage& src) const noexcept final override {
                assert(&dst != &src);
                move_construct(dst, src);
                destroy(src);
            }
            virtual void copy_assign(storage& dst, const storage& src) const final override {
                assert(src.manager == this);
                if (dst.manager->type == type) {
                    reinterpret_cast<T&>(dst.data[0]) = reinterpret_cast<const T&>(src.data[0]);
                } else if (dst.manager->is_internal) {
                    if constexpr (std::is_nothrow_copy_constructible_v<T>) {
                        if (!dst.manager->is_trivially_destructible)
                            dst.manager->destroy(dst);
                        copy_construct(dst, src);
                    } else if (dst.manager->is_trivial) {
                        alignas(soo_align) soo_storage tmp = dst.data;
                        SCOPE_FAIL { dst.data = tmp; };
                        copy_construct(dst, src);
                    } else {
                        T tmp = reinterpret_cast<const T&>(src.data[0]);
                        if (!dst.manager->is_trivially_destructible)
                            dst.manager->destroy(dst);
                        new(static_cast<void*>(&dst.data[0])) T(std::move(tmp));
                        dst.manager = this;
                    }
                } else {
                    void* old_dst_ptr = dst.ptr;
                    if constexpr (std::is_nothrow_copy_constructible_v<T>) {
                        copy_construct(dst.data, src.data);
                    } else {
                        SCOPE_FAIL { dst.ptr = old_dst_ptr;; };
                        copy_construct(dst.data, src.data);
                    }
                    dst.manager->delete_(old_dst_ptr);
                    dst.manager = this;
                }
            }
            virtual void move_assign(storage& dst, storage& src) const final override {
                assert(src.manager == this);
                if (dst.manager->type == type) {
                    reinterpret_cast<T&>(dst.data[0]) = reinterpret_cast<T&&>(src.data[0]);
                } else {
                    if (!dst.manager->is_trivially_deletable)
                        dst.manager->delete_(dst);
                    move_construct(dst, src);
                }
            }
            virtual void swap(storage& this_, storage& other) const noexcept final override {
                assert(this_.manager == this);
                using std::swap;
                if (other.manager->type == type) {
                    swap(reinterpret_cast<T&>(this_.data[0]), reinterpret_cast<T&>(other.data[0]));
                } else if (other.manager->is_internal) {
                    alignas(soo_align) soo_storage tmp;
                    move_construct_and_destroy(tmp, this_.data);
                    if (other.manager->is_trivial)
                        this_.data = other.data;
                    else
                        other.manager->move_construct_and_destroy(this_.data, other.data);
                    move_construct_and_destroy(other.data, tmp);
                    swap(this_.manager, other.manager);
                } else {
                    void* old_dst_ptr = other.ptr;
                    move_construct_and_destroy(other.data, this_.data);
                    this_.ptr = old_dst_ptr;
                    swap(this_.manager, other.manager);
                }
            }
            virtual void destroy(storage& dst) const noexcept final override {
                assert(dst.manager == this);
                destroy(dst.data);
            }
            void destroy(soo_storage& dst) const noexcept {
                reinterpret_cast<T&>(dst[0]).~T();
            }
            virtual void delete_(storage& dst) const noexcept final override {
                assert(dst.manager == this);
                destroy(dst.data);
            }
            virtual void delete_(void*) const noexcept final override {
                assert(false);
                std::terminate();
            }
        };
    
        template <typename T>
        struct mem_manager<T, false> : mem_manager_base {
            mem_manager() noexcept
            : mem_manager_base(false, std::is_trivially_destructible_v<T>, std::is_trivial_v<T>,
                               anymorph_detail::zombie_storage<T>::zombie_ptr,
                               anymorph_detail::zombie_storage<T>::zombie, typeid(T))
            {}
            virtual void copy_construct(storage& dst, const storage& src) const final override {
                assert(src.manager == this);
                dst.ptr = copy_construct(src.ptr);
                dst.manager = this;
            }
            void* copy_construct(const void* src) const {
                return new T(*static_cast<const T*>(src));
            }
            virtual void move_construct(storage& dst, storage& src) const final override {
                assert(src.manager == this);
                dst.ptr = src.ptr;
                dst.manager = this;
                src.manager = &manager<anymorph_detail::empty_anymorph_value>;
            }
            virtual void move_construct(soo_storage&, soo_storage&) const noexcept final override {
                assert(false);
                std::terminate();
            }
            virtual void move_construct_and_destroy(soo_storage&, soo_storage&) const noexcept final override {
                assert(false);
                std::terminate();
            }
            virtual void copy_assign(storage& dst, const storage& src) const final override {
                assert(src.manager == this);
                if (dst.manager->type == type) {
                    *reinterpret_cast<T*>(dst.ptr) = *reinterpret_cast<const T*>(src.ptr);
                } else {
                    void* src_copy = copy_construct(src.ptr);
                    if (!dst.manager->is_trivially_deletable)
                        dst.manager->delete_(dst);
                    dst.ptr = src_copy;
                    dst.manager = this;
                }
            }
            virtual void move_assign(storage& dst, storage& src) const final override {
                assert(src.manager == this);
                if (dst.manager->type == type) {
                    *reinterpret_cast<T*>(dst.ptr) = std::move(*reinterpret_cast<T*>(src.ptr));
                } else {
                    if (!dst.manager->is_trivially_deletable)
                        dst.manager->delete_(dst);
                    dst.ptr = src.ptr;
                    dst.manager = this;
                    src.manager = &manager<anymorph_detail::empty_anymorph_value>;
                }
            }
            virtual void swap(storage& this_, storage& other) const noexcept final override {
                assert(this_.manager == this);
                using std::swap;
                if (!other.manager->is_internal) {
                    swap(this_.ptr, other.ptr);
                    swap(this_.manager, other.manager);
                } else {
                    void* tmp = this_.ptr;
                    if (other.manager->is_trivial)
                        this_.data = other.data;
                    else
                        other.manager->move_construct_and_destroy(this_.data, other.data);
                    other.ptr = tmp;
                    swap(this_.manager, other.manager);
                }
            }
            virtual void destroy(storage&) const noexcept final override {
                assert(false);
                std::terminate();
            }
            virtual void delete_(storage& dst) const noexcept final override {
                assert(dst.manager == this);
                delete_(dst.ptr);
            }
            virtual void delete_(void* p) const noexcept final override {
                delete static_cast<T*>(p);
            }
        };
    
    public:
        anymorph() noexcept
        : anymorph(anymorph_detail::empty_anymorph_value{})
        {}
        anymorph(const anymorph& other) {
            if (other.value.manager->is_internal_trivial)
                value = other.value;
            else
                other.value.manager->copy_construct(value, other.value);
        }
        anymorph(anymorph&& other) noexcept {
            if (other.value.manager->is_internal_trivial)
                value = other.value;
            else
                other.value.manager->move_construct(value, other.value);
        }
    
        template <typename U, std::enable_if_t<!std::is_same_v<std::decay_t<U>, anymorph>
                                             && std::is_copy_constructible_v<std::decay_t<U>>, int> = 0>
        anymorph(U&& src) : anymorph(std::in_place_type_t<U>{}, std::forward<U>(src)) {}
    
        template <typename U, typename... Args,
                   std::enable_if_t<!std::is_same_v<std::decay_t<U>, anymorph>
                && std::is_copy_constructible_v<std::decay_t<U>>
                && std::is_constructible_v<std::decay_t<U>, Args...>, int> = 0>
        explicit anymorph(std::in_place_type_t<U>, Args&&... args)
        : anymorph(delegate_t{}, std::in_place_type_t<U>{}, std::forward<Args>(args)...)
        {}
    
        template <typename U, typename V, typename... Args,
                   std::enable_if_t<!std::is_same_v<std::decay_t<U>, anymorph>
                && std::is_copy_constructible_v<std::decay_t<U>>
                && std::is_constructible_v<std::decay_t<U>, std::initializer_list<V>, Args...>, int> = 0>
        explicit anymorph(std::in_place_type_t<U>, std::initializer_list<V> il, Args&&... args)
        : anymorph(delegate_t{}, std::in_place_type_t<U>{}, il, std::forward<Args>(args)...)
        {}
    private:
        template <typename U>
        static void zombiefy(U* p) noexcept {
            anymorph_detail::zombie_storage<U>::initialize(p);
        }
        template <typename U, typename... Args>
        explicit anymorph(delegate_t, std::in_place_type_t<U>, Args&&... args) {
            using src_type = std::decay_t<U>;
            // assumed static_assert(std::is_nothrow_destructible_v<src_type>);
            src_type* p;
            if constexpr (is_soo_type_v<src_type>)
                p = new(static_cast<void*>(&value.data[0])) src_type(std::forward<Args>(args)...);
            else
                value.ptr = p = new src_type(std::forward<Args>(args)...);
            value.manager = &manager<src_type>;
            zombiefy(p);
        }
    public:
        ~anymorph() {
            if (!value.manager->is_trivially_deletable)
                value.manager->delete_(value);
        }
    
        anymorph& operator=(const anymorph& rhs) {
            if (value.manager->is_internal_trivial && rhs.value.manager->is_internal_trivial)
                value = rhs.value;
            else
                rhs.value.manager->copy_assign(value, rhs.value);
            return *this;
        }
        anymorph& operator=(anymorph&& rhs) {
            if (value.manager->is_internal_trivial && rhs.value.manager->is_internal_trivial)
                value = rhs.value;
            else
                rhs.value.manager->move_assign(value, rhs.value);
            return *this;
        }
    
        template <typename U, std::enable_if_t<!std::is_same_v<std::decay_t<U>, anymorph>
                                             && std::is_copy_constructible_v<std::decay_t<U>>, int> = 0>
        anymorph& operator=(U&& rhs) {
            emplace<U>(std::forward<U>(rhs));
            return *this;
        }
    
        template<typename U, typename... Args,
                   std::enable_if_t<!std::is_same_v<std::decay_t<U>, anymorph>
                && std::is_copy_constructible_v<std::decay_t<U>>
                && std::is_constructible_v<std::decay_t<U>, Args...>, int> = 0>
        std::decay_t<U>& emplace(Args&&... args) {
            return emplace<U>(delegate_t{}, std::forward<Args>(args)...);
        }
    
        template<typename U, typename V, typename... Args,
                   std::enable_if_t<!std::is_same_v<std::decay_t<U>, anymorph>
                && std::is_copy_constructible_v<std::decay_t<U>>
                && std::is_constructible_v<std::decay_t<U>, std::initializer_list<V>, Args...>, int> = 0>
        std::decay_t<U>& emplace(std::initializer_list<V> li, Args&&... args) {
            return emplace<U>(delegate_t{}, li, std::forward<Args>(args)...);
        }
    private:
        template <typename U, typename... Args>
        std::decay_t<U>& emplace(delegate_t, Args&&... args) {
            using src_type = std::decay_t<U>;
            reset();
            src_type* p;
            if constexpr (is_soo_type_v<src_type>)
                p = new(static_cast<void*>(&value.data[0])) src_type(std::forward<Args>(args)...);
            else
                value.ptr = p = new src_type(std::forward<Args>(args)...);
            value.manager = &manager<src_type>;
            initialize_zombie(p);
            return *p;
        }
    public:
        void reset() noexcept {
            if (!value.manager->is_trivially_deletable)
                value.manager->delete_(value);
            value.manager = &manager<anymorph_detail::empty_anymorph_value>;
        }
    
        void swap(anymorph& other) noexcept {
            using std::swap;
            if (value.manager->is_internal_trivial && other.value.manager->is_internal_trivial) {
                swap(value, other.value);
            } else if (!value.manager->is_internal && !other.value.manager->is_internal) {
                swap(value.ptr, other.value.ptr);
                swap(value.manager, other.value.manager);
            } else {
                value.manager->swap(value, other.value);
            }
        }
        void swap(anymorph&& other) noexcept {
            swap(other);
        }
    
        bool has_value() const noexcept { return type() != manager<anymorph_detail::empty_anymorph_value>.type; }
        const std::type_info& type() const noexcept { return value.manager->type; }
    
        template <typename T, std::size_t S, std::size_t A>
        friend T any_cast(anymorph<S, A>& v);
    private:
        storage value;
    
        friend void swap(anymorph& x, anymorph& y) noexcept { return x.swap(y); }
        friend void swap(anymorph& x, anymorph&& y) noexcept { return x.swap(y); }
        friend void swap(anymorph&& x, anymorph& y) noexcept { return x.swap(y); }
        friend void swap(anymorph&& x, anymorph&& y) noexcept { return x.swap(y); }
    
        template <typename T, std::size_t S, std::size_t A>
        friend const T* any_cast(const anymorph<S, A>* op);
        template <typename T, std::size_t S, std::size_t A>
        friend const T* anymorph_cast(const anymorph<S, A>* op);
    };
    template <std::size_t S, std::size_t A>
    template <typename T>
    typename anymorph<S, A>::template mem_manager<T> anymorph<S, A>::manager;
    
    template <typename T, std::size_t S, std::size_t A>
    const T* any_cast(const anymorph<S, A>* op) {
        return op && typeid(std::decay_t<T>) == op->type() ? static_cast<const T*>(op->value.get_ptr()) : nullptr;
    }
    template <typename T, std::size_t S, std::size_t A>
    T* any_cast(anymorph<S, A>* op) {
        return const_cast<T*>(any_cast<T>(static_cast<const anymorph<S, A>*>(op)));
    }
    template <typename T, std::size_t S, std::size_t A>
    T any_cast(const anymorph<S, A>& op) {
        using U = std::remove_cv_t<std::remove_reference_t<T>>;
        static_assert(std::is_constructible_v<T, const U&>);
        if (auto p = any_cast<U>(&op))
            return *p;
        else
            throw std::bad_any_cast();
    }
    template <typename T, std::size_t S, std::size_t A>
    T any_cast(anymorph<S, A>& op) {
        using U = std::remove_cv_t<std::remove_reference_t<T>>;
        static_assert(std::is_constructible_v<T, U&>);
        if (auto p = any_cast<U>(&op))
            return *p;
        else
            throw std::bad_any_cast();
    }
    template <typename T, std::size_t S, std::size_t A>
    T any_cast(anymorph<S, A>&& op) {
        using U = std::remove_cv_t<std::remove_reference_t<T>>;
        static_assert(std::is_constructible_v<T, U>);
        if (auto p = any_cast<U>(&op))
            return std::move(*p);
        else
            throw std::bad_any_cast();
    }
    
    template <typename T, std::size_t S, std::size_t A>
    const T* anymorph_cast(const anymorph<S, A>* op) {
        using U = std::decay_t<T>;
        if (!op)
            return nullptr;
        try {
            std::rethrow_exception(op->value.manager->zombie_ptr);
        } catch (U* p) {
            const char* q = reinterpret_cast<const char*>(p);
            auto offset = q - op->value.manager->zombie_data;
            return reinterpret_cast<const T*>(static_cast<const char*>(op->value.get_ptr()) + offset);
        } catch (void*) {
            return nullptr;
        }
    }
    template <typename T, std::size_t S, std::size_t A>
    T* anymorph_cast(anymorph<S, A>* op) {
        return const_cast<T*>(anymorph_cast<T>(static_cast<const anymorph<S, A>*>(op)));
    }
    template <typename T, std::size_t S, std::size_t A>
    T anymorph_cast(const anymorph<S, A>& op) {
        using U = std::remove_cv_t<std::remove_reference_t<T>>;
        static_assert(std::is_constructible_v<T, const U&>);
        if (auto p = anymorph_cast<U>(&op))
            return *p;
        else
            throw bad_anymorph_cast();
    }
    template <typename T, std::size_t S, std::size_t A>
    T anymorph_cast(anymorph<S, A>& op) {
        using U = std::remove_cv_t<std::remove_reference_t<T>>;
        static_assert(std::is_constructible_v<T, U&>);
        if (auto p = anymorph_cast<U>(&op))
            return *p;
        else
            throw bad_anymorph_cast();
    }
    template <typename T, std::size_t S, std::size_t A>
    T anymorph_cast(anymorph<S, A>&& op) {
        using U = std::remove_cv_t<std::remove_reference_t<T>>;
        static_assert(std::is_constructible_v<T, U>);
        if (auto p = anymorph_cast<U>(&op))
            return std::move(*p);
        else
            throw bad_anymorph_cast();
    }
    

    kleines Testprogramm:

    #include <iostream>
    #include <vector>
    
    #include "anymorph.hpp"
    struct base {
        base(int i) : value(i) {}
        void foo() { std::cout << "base::foo() " << value << '\n'; }
        int value;
    };
    template <int I>
    struct derived : base
    {
        derived() : base(I) { std::cout << (void*)this << " derived<" << I << ">::derived()\n"; }
        derived(const derived&) : base(I) { std::cout << (void*)this << " derived<" << I << ">::derived(const derived&)\n"; }
        derived(derived&&) : base(I) { std::cout << (void*)this << " derived<" << I << ">::derived(derived&&)\n"; }
        derived& operator=(const derived&) { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(const derived&)\n"; return *this; }
        derived& operator=(derived&&) noexcept { std::cout << (void*)this << " derived<" << I << ">& derived<" << I << ">::operator=(derived&&)\n"; return *this; }
        ~derived() noexcept { std::cout << (void*)this << " derived<" << I << ">::~derived()\n"; }
        void foo() { std::cout << (void*)this << " derived<" << I << ">::foo()\n"; }
    };
    
    struct X : derived<0>, virtual derived<1>, derived<2> {
        void foo() { std::cout << (void*)this << " X::foo()\n"; }
    };
    int main() {
        try {
            anymorph<> test(X{});
            any_cast<X&>(test).foo();
            anymorph_cast<derived<0>&>(test).foo();
            anymorph_cast<derived<1>&>(test).foo();
            anymorph_cast<derived<1>&>(test).foo();
            std::vector<anymorph<>> v;
            std::cout << "v.emplace_back(derived<0>{});\n";
            v.emplace_back(derived<0>{});
            std::cout << "v.push_back(derived<1>{});\n";
            v.push_back(derived<1>{});
            std::cout << "v.emplace_back(derived<2>{});\n";
            v.emplace_back(derived<2>{});
            std::cout << "***\n";
            for ( auto& x : v )
                anymorph_cast<base&>(x).foo();
            std::cout << "v[0] = std::move(v[0]);\n";
            v[0] = std::move(v[0]);
            std::cout << "v[0] = std::move(v[2]);\n";
            v[0] = std::move(v[2]);
            std::cout << "v[0] = v[0];\n";
            v[0] = v[0];
            std::cout << "v[0] = v[1];\n";
            v[0] = v[1];
            std::cout << "swap(v[0],v[0]);\n";
            swap(v[0],v[0]);
            std::cout << "swap(v[0],v[2]);\n";
            swap(v[0],v[2]);
            std::cout << "return 0;\n";
            std::cout << "***\n";
            anymorph_cast<base&>(v[2]).foo();
            anymorph_cast<base&>(v[1]).foo();
            anymorph_cast<base&>(v[0]).foo();
        } catch (std::exception& e) {
            std::cout << e.what() << '\n';
        }
    }
    

    Edit: ein paar Fehlerkorrekturen.


Anmelden zum Antworten