[C++03] unrestricted unions - hack



  • Hallo,

    ich bilde mir ein, einen variant-Datentypen implementieren zu müssen.
    Seit 10 Jahren ist das auch keine Kunst mehr; aber ich habe hier eben auch ausschließlich einen 10 Jahre alten Compiler: MSVC 2010.

    Fällt euch was hübscheres (muss nicht generisch sein) ein, als mit char[] für speicher und alignment rumzufrickeln?

    Und:

    struct type_a {};
    struct type_b { unsigned data; };
    struct type_c { char data; };
    
    struct variant
    {
      enum type_type { EMPTY, A, B, C} type;
    
      char memory[
        max(
          max(
            sizeof(type_a)+alignof(type_a),
            sizeof(type_b)+alignof(type_b)
          ),
          sizeof(type_c)+alignof(type_c)
      )
      ];
    };
    

    und dann reinterpret_cast<T*>(memory) mit placement new und manuellem DTor-Aufruf.

    Oder hab ich da was vergessen / falsch gemacht?
    align_of weiß ich gerade nicht ausm kopf, obs das im MSVC10 schon gabm aber das sollte nicht so kompliziert sein.

    Danke 🙂



  • Wie wäre es mit sowas?

    #include <type_traits>
    #include <algorithm>
    
    template <typename... Ts>
    using variant_storage_t =
        std::aligned_storage_t<
            std::max({ sizeof(Ts)... }),
            std::max({ alignof(Ts)... })
        >;
    
    variant_storage_t<type_a, type_b, type_c> memory;
    

    Das finde ich "hübscher", weil es generisch ist und den Typen aus der Standardbibliothek verwendet, der für diesen Zweck vorgesehen ist (auch wenn es mit einem korrekt verwendeten char[] ebenfalls funktionieren sollte).

    Ergänzung: MSVC scheint sich da zu verschlucken, jedenfalls bei der Version, die ich verwende. Das hier sollte auch mit diesem Compiler funktionieren:

    template <typename... Ts>
    constexpr auto variant_size = std::max({ sizeof(Ts)... });
    template <typename... Ts>
    constexpr auto variant_alignment = std::max({ alignof(Ts)... });
    
    template <typename... Ts>
    using variant_storage_t =
        std::aligned_storage_t<
            variant_size<Ts...>,
            variant_alignment<Ts...>
        >;
    

    Auch möchte MSVC scheinbar, dass man für "over-aligned"-Typen (alignof(T) > alignof(std::max_align_t)) -D_ENABLE_EXTENDED_ALIGNED_STORAGE definiert, sonst verwendet er offenbar immer dasselbe Alignment wie std::max_align_t. Eventuell ist das hier am Ende doch simpler und etwas mehr "straightforward" (kürzer und weniger Fallstricke):

    template <typename... Ts>
    alignas(std::max({ alignof(Ts)... })) char memory[std::max({ sizeof(Ts)... })];
    

    Ich sehe hier effektiv eigentlich keinen Unterschied zu std::aligned_storage_t. Oder kennt jemand mögliche Probleme, wenn man das nicht verwendet?

    Edit: Oje! Ich hab das mit dem MSVC2010 ja total überlesen! Sorry, gelesen schon irgendwie, aber nur bis "20", dass da hinter sowas ne 10 kommen könnte, hab ich irgendwie nicht mehr auf dem Schirm 😉

    Das, was ich hier geposted habe ist leider C++14. Für VS2010 müsste ich nochmal nachrecherchieren, dafür habe ich aber gerade keine Zeit mehr. Später, oder jemand anderes hat ne Idee. Ohne alignas und alignof wirds auf jeden Fall pfriemeliger.



  • @unskilled sagte in [C++03] unrestricted unions - hack:

    Hallo,

    ich bilde mir ein, einen variant-Datentypen implementieren zu müssen.
    Seit 10 Jahren ist das auch keine Kunst mehr; aber ich habe hier eben auch ausschließlich einen 10 Jahre alten Compiler: MSVC 2010.

    So, ich versuchs nochmal, kurz un bündig:

    Generell brauchst du nur das größte Alignment zu berücksichtigen, da diese nur als Zweierpotenzen auftreten und daher immer auch alle gültigen niedrigeren Alignments erfüllen.

    Es macht Sinn, dem Compiler die Alignment-Anforderungen mitzuteilen und diese auch vom Compiler ermitteln zu lassen. Manuelles Management ist immer fehleranfällig und braucht disziplinierte Programmierer. Das müsste selbst unter VS2010 mit compilerspezifischen Attributen gehen wie __declspec(align(#)) und __alignof(). Prä-C++11-Versionen von GCC oder Clang müssten ähnliche Attribute unterstützen.

    Wenn diese Attribute nicht verfügbar sind, bleibt nur das Alignment manuell zu korrigieren. Dazu müsste das memory-Array <Größe des größten Typs> + <Maximale Alignmentanforderung> - 1 Bytes gross sein und die Adresse des Objekts im memory-Array um ein Offset verschoben werden (offset = (max_alignment - reinterpret_cast<uintptr_t>(&memory) % max_alignment) % max_alignment), mit dem das Alignment korrigiert wird. Für Arrays ist das allerdings eine recht pessimistische Methode. Hier würde es reichen, lediglich die Startadresse das Arrays auf das Alignment zu bringen und Padding hinzuzufügen, mit dem die Größe des variant auf ein Vielfaches des Alignment zu bringen.

    Alles in Allem wäre es recht aufwändig, das ohne alignas und alignof sauber und korrekt hinzubekommen. Es hat schon Vorteile, wenn sich der Compiler um die korrekte Startadresse (auf Stack (!) und auf Heap) kümmert und auch das nötige Padding übernimmt.



  • @Finnegan
    Danke für die ausführliche Antwort!
    Hab dann boost::variant genommen; wollte ich zwar eigentlich nicht im Interface haben, aber was solls. Besser als nur fast-richtig 😉

    bb 🙂



  • @unskilled Da gabs mal ein GotW zu dem Thema.

    // edit: GotW #85: Style Case Study #3: Construction Unions

    Leider kann ich den dreiteiligen Artikel "Discriminated Unions" von Alexandrescu auf den sich Sutter bezieht nirgends mehr finden.


  • Administrator

    @Swordfish sagte in [C++03] unrestricted unions - hack:

    Leider kann ich den dreiteiligen Artikel "Discriminated Unions" von Alexandrescu auf den sich Sutter bezieht nirgends mehr finden.

    Sie sind im Web-Archiv vorhanden:
    http://web.archive.org/web/20090507121614/http://www.ddj.com/cpp/184403821
    http://web.archive.org/web/20080214111511/http://www.ddj.com/cpp/184403828
    http://web.archive.org/web/20090507105414/http://www.ddj.com/cpp/184403834


Log in to reply