C++ - Struct Padding/Alignment

Schnelle objektorientierte, kompilierende Programmiersprache.
Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: C++ - Struct Padding/Alignment

Beitrag von fat-lobyte » Mi Feb 29, 2012 5:11 pm

Xin hat geschrieben:Mir geht's um eine Menge schwer zu lesenden Code
Schwer zu lesen ist nur die Implementierung. Wenn die passt, dann passt die. Wenn du sagen willst dass

Code: Alles auswählen

typedef PackedStruct <
    double, x,
    double, y
> Punkt;

Punkt p;
p.get<x>() = 5;
p.get<y>() = 2;
So viel schwerer ist als

Code: Alles auswählen

typedef struct {
    double x;
    double y;
} Punkt;

Punkt p;
p.x = 5;
p.y = 2;
, dann haben wir unterschiedliche Auffasungen von schwer. Wenn dir etwas Bequemeres als s.get<ABC>() einfällt, sag bescheid.
und besonders um struct firstmember {} und struct secondmember {}.
Das ist keine Hexerei: die werden zwar "NameTag"'s gennant, sind aber eigentlich nur eines: Namen. Diese Namen muss man halt vorher deklarieren, bevor man sie benutzt. Danach kannst du deine Variablen mit diesen NameTags ansprechen.
Ist der Unterschied wirklich so groß?

Code: Alles auswählen

// deine Version:
getX()
//meine Version:
get<X>() 
Das Ganze erscheint mir ein deutlich höheres Fehlerrisiko zu haben
Welche Fehler genau? Welches Risiko? Der Code, der tatsächlich etwas "tut" ist bis jetzt nur 1 Zeile lang und besteht aus Variablenzugriff. Alles andere ist nur drumherum, damit der Compiler die richtigen Typen und das richtige Offset auswählt.
als die Chance, dass (Wert & 1) auf irgendeiner Maschine mal ein anderes Ergebnis liefert.
Das hat jetzt nichts mit diesem Thema zu tun, oder?
Außerdem nein: wenn dieser Code sitzt, dann passt es auch. Egal auf welcher CPU - der Standard sollte hoffentlich der gleiche sein. Noch gibts Probleme bei den Compilern, in ein paar Jahren sollte sich das aber beruhigt haben.

Vielleicht sollte ich nochmal genau beschreiben was da passiert. Also:
deine Idee mit der Klasse hat mir gut gefallen, es sah ziemlich Sinnvoll aus.
Was mir dabei nicht passte war: möchte ich 6 Verschiedene "gepackte" Structs schreiben, muss ich nach deinem Vorschlag 6 verschiedene Klassen schreiben.
Habe ich in jedem dieser Structs 3-4 Member, dann brauche ich 6-8 get/set Methoden.
Möchte ich eine Variable hinzufügen, muss ich den Code für die deklaration an 4 (!) Stellen ändern:
1) deklaration der Variable
2) sizeof() des Structs
3) getter
4) setter
Möchte ich bei meiner Version was Ändern, muss ich nur eine Sache ändern: das typedef für den Namen.

Zusammenfassend: mein "PackedStruct" ist deine Klasse "A" aus deinem Vorschlag, nur allgemeiner. Also wo genau ist das jetzt das Problem?
Bist Du sicher, dass Du die richtige Lösung für Dein Problem gefunden hast?
Ein C++-Schlüsselwort "packed" wäre mir natürlich lieber. Aber sowas ist anscheinend nicht im Standard vorgesehen (wenn nicht, bitte, bitte korrigieren!).
Also ja, mit ein bisschen Verfeinerung wäre ich ziemlich mit der Lösung für mein Problem zufrieden.
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: C++ - Struct Padding/Alignment

Beitrag von Xin » Do Mär 01, 2012 11:00 am

fat-lobyte hat geschrieben:
Xin hat geschrieben:Mir geht's um eine Menge schwer zu lesenden Code
Schwer zu lesen ist nur die Implementierung. Wenn die passt, dann passt die. Wenn du sagen willst dass

Code: Alles auswählen

typedef PackedStruct <
    double, x,
    double, y
> Punkt;

Punkt p;
p.get<x>() = 5;
p.get<y>() = 2;
So viel schwerer ist als

Code: Alles auswählen

typedef struct {
    double x;
    double y;
} Punkt;

Punkt p;
p.x = 5;
p.y = 2;
Die Liste gefällt mir nicht.

Code: Alles auswählen

typedef struct 
{
  double x, y, z 
} Punkt;
führt zu

Code: Alles auswählen

typedef PackedStruct <
    double, x, y, z
> Punkt;
Sollte y ein Datentyp sein - im Idealfall sogar noch ein scheinbar kompatibler Datentyp, weil er mit einem double konstruiert werden kann, dann könnten merkwürdige Effekte auftreten.

Konstruiertes Problem - ich weiß. Gefällt mir trotzdem nicht.


Eine Implementierung im Hintergrund ist die eine Sache, ich glaube, hier würde ich einen Codegenerator vorziehen, der mir aus aus einer struct eine Klasse mit den erforderlichen Funktionen getX und getY schreibt.

Codegeneratoren sind immer ein Zeichen dafür, dass einer Sprache etwas fehlt. In dem Fall eine Standardisierung für ein Schlüsselwort "packed".
Das sollte ich mir vorsichtshalber gleich mal aufschreiben...
fat-lobyte hat geschrieben:Möchte ich bei meiner Version was Ändern, muss ich nur eine Sache ändern: das typedef für den Namen.
Die Alternative ist, den Präprozessor zu benutzen, um den Code entsprechend anzupassen, z.B. für Visual C++.

Code: Alles auswählen

#ifdef _MSC_VER  
#define __packed  
#pragma pack(1)  
#endif 

typedef __packed struct 
{
  char a;
  double b;
}
Der Präprozessor ist ein veraltetes Konzept.
Aber nützlich.

Keine Ahnung, ob das für alle C-Compiler auf dieser Welt eine Lösung darstellt.
Deine Lösung hält sich vielleicht an den Standard, aber funktioniert nichtmals auf dem aktuellen, stabilen GCC.
Heißt, dass Deine Lösung in den nächsten Jahren nur auf experimenteller Basis läuft.
Heißt für mich, dass Deine Lösung in ein paar Jahren, wenn sich das bis dahin beruhigt haben sollte, durchaus eine Lösung darstellt.
Was machen wir bis dahin?
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: C++ - Struct Padding/Alignment

Beitrag von fat-lobyte » Do Mär 01, 2012 11:15 am

Xin hat geschrieben:

Code: Alles auswählen

typedef PackedStruct <
    double, x, y, z
> Punkt;
Interessante Sache. Vielleicht kann ich das auch noch einbauen... Mit Templates kann man viel machen :)
Aber zuerst gehören mal so Dinge wie Konstruktor und const-correctness gelöst.
Konstruiertes Problem - ich weiß. Gefällt mir trotzdem nicht.
Das Leben ist hart. Mir gefallen Präprozessor und Codegeneratoren noch weniger.
Xin hat geschrieben:Deine Lösung hält sich vielleicht an den Standard, aber funktioniert nichtmals auf dem aktuellen, stabilen GCC.
Macht nix. Mein Projekt wird nicht in diesem, dem nächsten und auch nicht im übernächsten Jahr ein "Release" erleben, und ich habe keine Lust jetzt schon veralteten Code zu schreiben, weil ein Compiler meint "sorry, ich kann das noch nicht".

Die Unterstützung wird noch kommen, bis dahin gibts sehr sehr (seeehhr!) viel, was es noch über C++ zu lernen gibt - und ich fange eben jetzt schon damit an.

Heißt für mich, dass Deine Lösung in ein paar Jahren, wenn sich das bis dahin beruhigt haben sollte, durchaus eine Lösung darstellt.
Was machen wir bis dahin?
__attribute__ ((packed)).
Mein Projekt kompiliert sowieso nur auf GCC >= 4.6, bis ich ne Zufriedenstellende Lösung gefunden habe und der C++11 support angekommen ist, kann ich ruhig unportabel bleiben. Mir gings hier nur um eine schöne, dauerhafte Lösung.

ps.:
Xin hat geschrieben:... abgesehen davon dass ich "Variadic Templates" noch nicht kenne (ich muss mir mal 'ne Woche Urlaub nehmen und C++11 mal antesten...) ...
Ein wirklich guter Einstieg sind die Talks von der GoingNative, die in diesem Thread schon gepostet wurden: http://www.proggen.org/forum/viewtopic.php?f=61&t=4844
Ziemlich aufschlussreich waren:
http://channel9.msdn.com/Events/GoingNa ... pp11-Style
http://channel9.msdn.com/Events/GoingNa ... re-Funadic (ich mag den Kerl :-) )

__attribute((sehenswert)) ;-)
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: C++ - Struct Padding/Alignment

Beitrag von Xin » Do Mär 01, 2012 11:29 am

fat-lobyte hat geschrieben:
Xin hat geschrieben:

Code: Alles auswählen

typedef PackedStruct <
    double, x, y, z
> Punkt;
Interessante Sache. Vielleicht kann ich das auch noch einbauen... Mit Templates kann man viel machen :)
Aber zuerst gehören mal so Dinge wie Konstruktor und const-correctness gelöst.
Poste das mal. Nur weil ich dagegen bin, heißt das nicht, dass ich nicht interessiert wäre :-)
fat-lobyte hat geschrieben:Das Leben ist hart.
Hehe, ich mag Deine Einstellung. ^^
Das erinnert mich sehr an mich.
"Always listen to experts - they tell you what can't be done and why. Then do it."
Nur, dass ich heute der 'expert' bin und Du umsetzt, was ich ablehne.
Finde ich gut, nur so geht's weiter.
fat-lobyte hat geschrieben:Mir gefallen Präprozessor und Codegeneratoren noch weniger.
C++ ist auch nur ein Präprozessor und Codegenerator. ;-)
Und wenn ich mit Genesys einfach die Dinge hübscher formulieren kann, die ich anderswo per Codegenerator erzeugen würde und das ganze dann als "Programmiersprache" bezeichne wird da auch nichts anderes draus.

Tatsächlich ist eins der nächsten Ziele mit Genesys, wieder C++ Code zu generieren, die abstraktere Konstrukte in C++ runterbrechen. C++ hieß zuerst Cfront - für "C Frontend". Es war ein C-Codegenerator.
fat-lobyte hat geschrieben:
Xin hat geschrieben:... abgesehen davon dass ich "Variadic Templates" noch nicht kenne (ich muss mir mal 'ne Woche Urlaub nehmen und C++11 mal antesten...) ...
Ein wirklich guter Einstieg sind die Talks von der GoingNative, die in diesem Thread schon gepostet wurden: http://www.proggen.org/forum/viewtopic.php?f=61&t=4844
Ziemlich aufschlussreich waren:
http://channel9.msdn.com/Events/GoingNa ... pp11-Style
http://channel9.msdn.com/Events/GoingNa ... re-Funadic (ich mag den Kerl :-) )
Vielen Dank.
Momentan stecke ich noch ziemlich im proggen.org-CMS und Genesys fest und als Arbeitnehmer fehlen mir etwas die Sommersemesterferien. ;)

Mal gucken, wo und wann ich das unterbringe.
Die Links werden auf jeden Fall in der entsprechenden Linksammlung untergebracht - auf der genesys-Website. Da heißt's erst recherchieren, dann Genesys weiterentwickeln. Man will ja auch nix schlechter machen, wenn es besseres gibt.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: C++ - Struct Padding/Alignment

Beitrag von fat-lobyte » Do Mär 01, 2012 10:26 pm

fat-lobyte hat geschrieben:
gcc-4.6 hat geschrieben:size_sum.cpp:7:67: sorry, unimplemented: cannot expand 'Ts ...' into a fixed-length argument list
Ok, mit GCC-4.7 funktioniert zwar das, aber dafür was anderes nicht. Hab mal nen Bug-Report eröffnet: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52454
Mal kucken, was sich da tut.

Destruktoren funktionieren jetzt (mit Clang natürlich ^^), default konstruktor sollte nicht so schwierig sein, aber nicht-triviale konstruktoren werden lustig.
Vielleicht werde ich auch einfach ein paar Monate warten und dann das ding etwas anders angehen, und mich an bekannten Implementierungen orientieren (z.B. std::tuple<>).

Ist allerdings nicht so leicht die Geschichte, weil man nicht weiß ob man selbst einfach zu blöd ist (sprich die Sprache nicht richtig verstanden hat) oder der Compiler schuld ist.
Normalerweise gilt bei mir eisern: DU bist schuld, und nicht der Compiler. Aber wenn der Compiler aus dem SVN kommt und die Sachen erst vor wenigen Wochen eingecheckt wurden, muss man das wohl etwas lockerer sehen...
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Beitrag von fat-lobyte » Fr Apr 20, 2012 11:34 am

So. Nachdem der GCC-Bug endlich behoben wurde, kann man auch mit GCC aus dem SVN den Code kompilieren.

Das bedeutet, dieser Code kompiliert mit GCC >= 4.8 und Clang >= 3.1.
MSVC unterstützt zurzeit nicht genug von C++.


Nochmals:

Der Zweck dieses Templates ist es, eine stark vereinfachtes "struct" zu deklarieren, das allerdings garantiert keinem Padding unterliegt - das bedeutet alle Datenmember liegen hintereinander (in Deklarationsreihenfolge) und ohne Zusatzbytes im Speicher.
Dies kann beispielsweise nützlich sein, um ein Strukturiertes Objekt direkt über das Netzwerk zu senden und auf einem anderen System zu empfangen, ohne auf mögliches Padding Rücksicht nehmen zu müssen.


Die "Deklaration" läuft über ein typedef, wobei vorher die Namen der Datenmember als leere structs deklariert werden müssen:

Code: Alles auswählen

#include "packedstruct.hpp"

struct age {}; // "Name" für den ersten Member
struct weight {}; // "Name" für den zweiten Member
struct cupsize {};

typedef PackedStruct<
    unsigned short, age,
    double, weight,
    char, cupsize
> PackedTrivial;
Anschließend kann ein Objekt konstruiert werden, entweder mit oder ohne Argumente für den Konstruktor.
Der Zugriff erfolgt über die get()-Methode, mit den zuvor deklarierten "tag"-Typen als templateargument:

Code: Alles auswählen

PackedTrivial trivial_init{21, 57.4, 'B'};
PackedTrivial trivial_uninit;

trivial_uninit.get<weight>() = 61.3;
assert(trivial_init.get<weight>()  == 57.4); 
Wird das Objekt zerstört, werden die Destruktoren der einzelnen Member aufgerufen.
Auch die const-Correctness sollte passen, d.H. auf einen const-Member wird nur eine const-Referenz zurückgegeben.

Dabei gilt IMMER die Garantie, dass das sizeof() des Typs genau der Summer der sizeof()'s der Membertypen entspricht.


Einschränkungen:
  • Hat ein Member einen Konstruktor, der mehrere Argumente braucht, muss man bei der Initialisierung ein temporäres Objekt übergeben:

    Code: Alles auswählen

    class MyType; // hat einen Konstruktor MyType(int, double)
    
    struct size {};
    struct myval {};
    
    typedef nuke_ms::PackedStruct
    <
        std::size_t, size,
        MyType,      myval
    > PackedNonTrivial;
    
    PackedNonTrivial nontrivial{33, MyType{12, 99.999}}; // <-- MyType muss angegeben werden
    
  • Referenztypen werden nicht unterstützt. Diese kann man in std::reference_wrapper<> packen. (Ich glaube diesen Fall müsste man auch einbauen können, hatte aber noch keinen Bedarf dafür. Wenn das jemand machen will, nur zu.)

Hier ist der Code:
packedstruct.hpp

Code: Alles auswählen

#ifndef PACKEDSTUCT_HPP_INCLUDED
#define PACKEDSTUCT_HPP_INCLUDED

#ifndef PACKEDSTRUCT_POD_ONLY
#   define PACKEDSTRUCT_NONTRIVIAL
#endif


namespace packedstruct_detail {

template <
    std::size_t Offset,
    typename VarType, typename NameTag,
    typename... Rest
>
struct PackedStructMember
{
    static constexpr std::size_t offset = Offset;
    typedef VarType vartype;
    typedef NameTag nametag;

    typedef PackedStructMember<offset + sizeof(vartype), Rest...> next;
    static constexpr std::size_t list_size = sizeof(vartype) + next::list_size;

    static const vartype& access(const char* base) // const
    {
        return *reinterpret_cast<const vartype*>(base + offset);
    }

    static vartype& access(char* base)
    {
        return *reinterpret_cast<vartype*>(base + offset);
    }

    static bool all_equal(const char* base, const char* other_base)
    {
        return (access(base) == access(other_base))
            && next::all_equal(base, other_base);
    }

#ifdef PACKEDSTRUCT_NONTRIVIAL
    static void construct(char* member_ptr)
    {
        // use placement new to initialize
        new (member_ptr) vartype{};

        // defer to next element initialization
        next::construct(member_ptr + sizeof(vartype));
    }

    template <typename ArgumentType, typename... ArgTypeRest>
    static void construct(
        char* member_ptr,
        ArgumentType&& arg,
        ArgTypeRest... args_rest
    )
    {
        // use placement new to initialize, forward arguments
        new (member_ptr) vartype(std::forward<ArgumentType>(arg));

        next::construct(
            member_ptr + sizeof(vartype),
            std::forward<ArgTypeRest>(args_rest)...
        );
    }

    static void destruct(char* member_ptr)
    {
        // call destructor explicitly
        reinterpret_cast<vartype*>(member_ptr)->~vartype();

        // call next node with pointer after the current element
        next::destruct(member_ptr + sizeof(vartype));
    }
#endif
};

template <std::size_t Offset, typename VarType, typename NameTag>
struct PackedStructMember<Offset, VarType, NameTag>
{
    static constexpr std::size_t offset = Offset;
    typedef VarType vartype;
    typedef NameTag nametag;

    // we are at the last member, the size of the list is just the size of our
    // variable type
    static constexpr std::size_t list_size = sizeof(vartype);

    static const vartype& access(const char* base) // const
    {
        return *reinterpret_cast<const vartype*>(base + offset);
    }

    static vartype& access(char* base)
    {
        return *reinterpret_cast<vartype*>(base + offset);
    }

    static bool all_equal(const char* base, const char* other_base)
    { return (access(base) == access(other_base)); }

#ifdef PACKEDSTRUCT_NONTRIVIAL
    static void construct(char* member_ptr)
    {
        // use placement new to initialize
        new (member_ptr) vartype{};
    }

    template <typename ArgumentType>
    static void construct(char* member_ptr, ArgumentType&& arg)
    {
        // use placement new to initialize, forward arguments
        new (member_ptr) vartype(std::forward<ArgumentType>(arg));
    }

    static void destruct(char* member_ptr)
    {
        // call destructor explicitly
        reinterpret_cast<vartype*>(member_ptr)->~vartype();
    }
#endif
};

// Search for a member by name tag.
template <typename MemberList, typename NameTag>
struct FindMemberByNameTag
{
    // this means we don't have it, ask the next one
    typedef typename FindMemberByNameTag<typename MemberList::next, NameTag>::member
        member;
};

// specialization, if the searched name tag is our name tag
template <typename MemberList>
struct FindMemberByNameTag<MemberList, typename MemberList::nametag>
{
    typedef MemberList member;
};


/** Packed struct.
 * This class represents a simplified struct type that provides
 * construction/destruction and access by reference, similar to a normal
 * C++-struct.
 * However, this class is guaranteed to be packed, that means it has NO padding.
 * All members are stored consecutively in declaration order, without gaps.
 *
 * To "declare" a struct, one has to first declare "tag"-types. These types
 * can and should be empty structs, because only their names will be used and no
 * objects will be created.
 * A member is declared in the struct by passing a pair of member type and "tag"
 * type as template argument. It is recommended to typedef the struct for ease
 * of use.
 *
 * @code
struct age {};
struct weight {};
struct cupsize {};

typedef PackedStruct<
    unsigned short, age,
    double, weight,
    char, cupsize
> MyType;
 * @endcode
 *
 * Upon construction of an object of this type, the members are initialized
 * either by a default constructor or, when passing arguments to the
 * constructor, with the corresponding arguments.
 *
 * Upon destruction, the destructor of each member is called.
 *
 * To access a member, use the get() method and pass the "tag" type as template
 * argument.
 *
 * @tparam Members A list of member type/"tag"-type pairs. Each tag type will
 * be used to access one particular member of the corresponding type.
*/
template <typename... Members>
class PackedStruct {
    static_assert(sizeof...(Members) % 2 == 0,
        "Members must be added in pairs of Variable-Type / Name-Tag"
    );

    typedef PackedStructMember<0, Members...> MemberList;

    char data[MemberList::list_size];

public:
#ifdef PACKEDSTRUCT_NONTRIVIAL

    /** Default constructor.
     * Default-constructs all members
    */
    PackedStruct()
    {
        MemberList::construct(data);
    }

    /** Constructor.
     * Construct all members with an initializer.
     * The number of arguments must match the number of struct members.
     *
     * @param args Arguments to pass to the constructors of the members.
    */
    template <typename... ArgTypes>
    explicit PackedStruct(ArgTypes&&... args)
    {
        static_assert(
            sizeof...(ArgTypes) == sizeof...(Members) / 2,
            "Number of constructor arguments does not match number of struct members."
        );

        MemberList::construct(data, std::forward<ArgTypes>(args)...);
    }

    /** Destructor.
     * Calls destructor of each data member
     */
    ~PackedStruct()
    {
        MemberList::destruct(data);
    }
#endif

    /** Access data member. */
    template <typename NameTag>
    typename FindMemberByNameTag<MemberList, NameTag>::member::vartype& get()
    {
        return FindMemberByNameTag<MemberList, NameTag>::member::access(data);
    }

    /** Access data member.
     * @tparam NameTag "tag" type that was used as a name for a member in the
     * template arguments of this class.
    */
    template <typename NameTag>
    const typename FindMemberByNameTag<MemberList, NameTag>::member::vartype&
    get() const
    {
        return FindMemberByNameTag<MemberList, NameTag>::member::access(data);
    }

    /** Compare two objects in a member-by-member way.*/
    bool operator == (const PackedStruct& other) const
    {
        return MemberList::all_equal(data, other.data);
    }
};

} // namespace packedstruct_detail

using packedstruct_detail::PackedStruct;

#endif // ifndef PACKEDSTUCT_HPP_INCLUDED
UND NUR HEUTE, GIBTS GRATIS DAZU: Ein Unit-Test, speziell zu geschnitten auf diese Klasse!
test_packedstruct.cpp

Code: Alles auswählen

// test_packedstruct.cpp

#include <iostream>
#include <cstring>
#include <cstdlib>

#include "packedstruct.hpp"

static int return_value = 0;

#define TEST_ASSERT(expr) if (!(expr)) \
    { \
        std::cout<<"\n* Test assertion \""<< #expr <<"\" failed.\n  Line "<< \
            __LINE__<<" in file "<<__FILE__<<"\n\n"; \
        return_value = EXIT_FAILURE; \
    }


int main()
{
    struct age {};
    struct weight {};
    struct cupsize {};

    typedef PackedStruct<
        unsigned short, age,
        double, weight,
        char, cupsize
    > PackedTrivial;

    std::cout<<"Struct has size: "<<sizeof(PackedTrivial)<<'\n';

    // check struct sizes
    TEST_ASSERT(sizeof(PackedTrivial) ==
        sizeof(unsigned short) + sizeof(double) + sizeof(char)
    );

    // write to uninitialized object, check if written correctly
    PackedTrivial trivial_uninit;

    trivial_uninit.get<age>() = 19;
    trivial_uninit.get<weight>() = 61.3;
    trivial_uninit.get<cupsize>() = 'C';

    std::cout<<"Member-assigned object:\n"
        <<"Age: "<<trivial_uninit.get<age>()<<'\n'
        <<"Weight: "<<trivial_uninit.get<weight>()<<'\n'
        <<"Cupsize: "<<trivial_uninit.get<cupsize>()<<'\n'
        <<'\n';

    TEST_ASSERT(trivial_uninit.get<age>() == 19);
    TEST_ASSERT(trivial_uninit.get<weight>()  == 61.3);
    TEST_ASSERT(trivial_uninit.get<cupsize>() == 'C');

    // now check the memory manually
    TEST_ASSERT(*reinterpret_cast<unsigned short*>(&trivial_uninit) == 19);
    TEST_ASSERT(
        *reinterpret_cast<double*>(
            reinterpret_cast<char*>(&trivial_uninit) + sizeof(unsigned short)
        ) == 61.3
    );
    TEST_ASSERT(
        *reinterpret_cast<char*>(
            reinterpret_cast<char*>(&trivial_uninit) + sizeof(unsigned short) + sizeof(double)
        ) == 'C'
    );

    // copy objects with memcopy, assert that they are still the same
    PackedTrivial other_trivial_uninit;
    memcpy(&other_trivial_uninit, &trivial_uninit, sizeof(PackedTrivial));
    TEST_ASSERT(trivial_uninit == other_trivial_uninit);


    // check struct initialization
    PackedTrivial trivial_init{21, 57.4, 'B'};

    std::cout<<"Constructor-initialized object:\n"
        <<"Age: "<<trivial_init.get<age>()<<'\n'
        <<"Weight: "<<trivial_init.get<weight>()<<'\n'
        <<"Cupsize: "<<trivial_init.get<cupsize>()<<'\n'
        <<'\n';

    TEST_ASSERT(trivial_init.get<age>() == 21);
    TEST_ASSERT(trivial_init.get<weight>()  == 57.4);
    TEST_ASSERT(trivial_init.get<cupsize>() == 'B');


    struct MyType {
        MyType() = delete;
        MyType(int x, double z) : _x{x}, _z{z}
        {
            std::cout<<"MyType constructed (this = "<<static_cast<void*>(this)<<") \n";
        }

        int _x;
        double _z;
    };

    struct size {};
    struct myval {};

    typedef PackedStruct
    <
        std::size_t, size,
        MyType,      myval
    > PackedNonTrivial;


#   ifdef TEST_SHOULDBREAK
    // this should not compile, since the MyType default constructor is deleted
    PackedNonTrivial nontrivial;
#   endif

    // note that this is not packed anymore, since MyType is not a packed data
    // structure. However, sizeof() and construction/access must still work.
    PackedNonTrivial nontrivial{33, MyType{12, 99.999}};

    std::cout<<"Constructor-initialized non-trivial object:\n"
        <<"size: "<<nontrivial.get<size>()<<'\n'
        <<"myval._x: "<<nontrivial.get<myval>()._x<<'\n'
        <<"myval._z: "<<nontrivial.get<myval>()._z<<'\n'
        <<'\n';

    TEST_ASSERT(nontrivial.get<size>() == 33);
    TEST_ASSERT(nontrivial.get<myval>()._x  == 12);
    TEST_ASSERT(nontrivial.get<myval>()._z == 99.999);

    const PackedNonTrivial c_nontrivial{31, MyType{9, 879.999}};
    TEST_ASSERT(c_nontrivial.get<size>() == 31);
    TEST_ASSERT(c_nontrivial.get<myval>()._x  == 9);
    TEST_ASSERT(c_nontrivial.get<myval>()._z == 879.999);

#   ifdef TEST_SHOULDBREAK
    // error: read-only variable is not assignable
    c_nontrivial.get<size>() = 12;
    c_nontrivial.get<myval>()._x = 44;
    c_nontrivial.get<myval>()._z = 93.122;
#   endif


    struct d{};

    typedef PackedStruct
    <
        const std::size_t, size,
        const double, d,
        const char, cupsize
    > PackedConstMembers;

    PackedConstMembers const_members{22, 3.14, 'D'};

    TEST_ASSERT(const_members.get<size>() == 22);
    TEST_ASSERT(const_members.get<d>() == 3.14);
    TEST_ASSERT(const_members.get<cupsize>() == 'D');

#   ifdef TEST_SHOULDBREAK
    // this should not compile, since the members are const
    // error: read-only variable is not assignable
    const_members.get<size>() = 4;
    const_members.get<d>() = 6.28;
    const_members.get<cupsize>() = 'C';
#   endif

    return return_value;
}
GREIFEN SIE ZU, MEINE DAMEN UND HERREN!


Übrigens habe ich dabei auch ein paar Tricks aus C++11 verwendet, das wichtigste dabei sind Variadic Templates. Wenns jemanden interessiert wie ich das Ding zusammengeschustert habe, kann sich den Code gern ansehen und dann einfach hier Fragen posten, ich helfe gerne.

mfg, fat-lobyte
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Haters gonna hate, potatoes gonna potate.

Antworten