2015-03-16 6 views
5

Ich habe diese Pseudo bitfield Umsetzung:Wie macht man dieses "template/constexpr" Konstrukt eleganter/weniger wortreich?

class Field { 
public: 
    constexpr Field(int i, int s) : index(i), size(s) {} 
    constexpr Field(const Field & prev, int s) : index(prev.index + prev.size), size(s) {} 
    int index, size; 
}; 

#define FIELD(name, i, s) constexpr static const Field name = {i, s}; 

template<typename T = quint32> 
class Flags { 
public: 
    Flags(T d = 0) : data(d) {} 
    inline T readField(const Field & f) { 
    return (data & getMask(f.index, f.size)) >> f.index; 
    } 
    inline void writeField(const Field & f, T val) { 
    data = (data & setMask(f.index, f.size)) | (val << f.index); 
    } 
private: 
    static constexpr T getMask(int i, int size) { 
    return ((1 << size) - 1) << i; 
    } 
    static constexpr T setMask(int pos, int size) { 
    return ~getMask(pos, size); 
    } 
    T data; 
}; 

Allerdings ist es ziemlich ausführliches in seiner jetzigen Form zu verwenden:

struct Test { 
    Flags<> flags; 
    FIELD(one, 0, 1) 
    FIELD(two, one, 2) 
}; 

Test t; 
t.flags.readField(t.one); 
t.flags.writeField(t.one, 1); 

Ich mag wäre es eleganter zu machen, so dass anstelle der Syntax oben ich dies einfach tun:

t.one.read(); 
t.one.write(1); 

Die Art und Weise habe ich versucht, für jeden Field eine Flags & zu tun haben, ist dies und implementieren read() und write() Methoden, die intern die Flags Ziele verwenden.

Dies erfordert jedoch, dass die Field auch eine Vorlage gemacht wird, die die Ausführlichkeit weiter erhöht, jetzt muss auch ein T für die Felder angegeben werden.

Ich versuchte T angegeben implizit ein Flags<T>::makeField() verwendet wird, hat aber es wurde bald ein Chaos der Inkompatibilität zwischen constexprt, static und ordentlichen Mitgliedern und Methoden, auto und so weiter, so dass nach im Kreis endlich eine Beratung von Menschen zu suchen, entschied mit mehr Erfahrung.

Natürlich gibt es die Anforderung, dass Fields Laufzeitspeicher nicht aufnehmen und so viele mögliche Ausdrücke während der Kompilierung aufgelöst werden.

+0

Ich denke nicht, die Syntax Sie wollen Nutzung ist möglich. Da die Felder statisch sind, können sie ihren Kontext nicht kennen. Es gibt also keine Möglichkeit, eine Funktion auf dem Feld zu haben, die mit einer Flags-Instanz funktioniert, ohne dass es ihnen mitgeteilt wird. In ähnlicher Weise zeigt die Art, wie Sie es in der ersten Instanz hatten, die Kehrseite - die Flags müssten angegeben werden, welches Feld verwendet werden soll. – qeadz

+0

@qeadz - seien Sie mein Gast, ich interessiere mich nicht für die tatsächlichen Implementierungsdetails, alles was mich interessiert ist, dass es keine zusätzlichen Laufzeitkosten hat und ordentlich in seiner Syntax ist. Es muss während des Kompilierens gelöst werden, und außen hübsch aussehen, drinnen ist es mir egal, auch wenn es ein Durcheinander ist :) –

+0

Ja Entschuldigung, ich habe meinen Kommentar zurückgezogen, weil ich bei weiterer Überlegung nicht sicher bin, ob der Compiler in der Lage sein wird um eine Menge von Lese-/Schreibvorgängen zur Kompilierzeit zu reduzieren, wenn das Endergebnis bekannt sein könnte, und dies ist ein notwendiges Merkmal. – qeadz

Antwort

1

Sieht nicht so aus, als würde jemand beißen, also werde ich nur die beiden Methoden erwähnen, die mir in den Sinn kamen.

Ich denke, der Schlüssel hier ist ein Feld, das einen bestimmten Wert ändern kann, ohne selbst Speicherplatz zu belegen. So sind die Sprach-Features, die dies zu erreichen auffallen würde:

Anonym Vereinigung die test.one.read() Art von Syntax gibt.

Leere Basis Optimierung die test.one() geben würde. Read() Art von Syntax.

Ein schnelles Beispiel für die erste ohne die eigentliche Bitlogik - alle Felder in diesem Beispiel ändern nur den gesamten Wert. Die bitweise Logik wäre trivial:

template< typename T > 
struct Bitmask 
{ 
    T m_val; 
}; 

template< typename BITMASK, int INDEX > 
struct Field : private BITMASK 
{ 
    int read() const { return BITMASK::m_val; } 
    void write(int i) { BITMASK::m_val = i; } 
}; 

struct Test 
{ 
    typedef Bitmask<int> Flags; 
    union 
    { 
     Flags m_flags; 
     Field<Flags,0> one; 
     Field<Flags,1> two; 
     Field<Flags,2> three; 
    }; 
}; 

Dies entspricht Ihre spezifische Nutzung aber mit dem Vorbehalt, dass das Feld auch als Templat. Nur als eine Randnotiz denke ich wirklich, dass die Dinge wirklich erledigt sind, aber es sollte wirklich test.m_flags.one.read() oder ähnlich sein, da wenn die Bits eindeutig, aber generisch benannt werden, dies jede Klasse mit einer Flags-Instanz zulässt habe mehrere von ihnen ohne Problem.

Die leere Basis Optimierung mit Funktionen Ich habe nicht gespottet, aber die Funktion würde ein Accessor-Objekt zurückgeben - ähnlich wie das Feld in Ihrem Beispiel, aber der erforderliche Parameter 'Flags &' wäre bereits gebunden.

Die leere Basis kann auch einzelne Vererbung und einige Casting erfordern.Auf der Plusseite könnte man meinen, es könnte genau die Anzahl der Bits in der Bitmaske unterstützt werden. Wenn also 3 Bits benötigt würden, könnte es als unsigniertes Zeichen gespeichert werden, aber nur die Funktionen eins(), zwei() und drei() wären vorhanden.

Wenn Sie möchten, und wenn ich etwas Zeit habe, könnte ich auch dieses Beispiel mock machen.

Soweit ich kann diese beiden Techniken sollte funktionieren, so würde mich interessieren, ob sie nicht tragbar sind und wenn ja, aus welchen Gründen.

BEARBEITEN Sie: Ein schneller Abschnitt über cppreference im Abschnitt über Gewerkschaften zeigt an, dass das Lesen von einem nicht aktiven Mitglied einer Union nicht vom Standard unterstützt wird. Die großen Compiler unterstützen es jedoch. Es gibt also ein Problem mit dem Ansatz.

+0

Ich denke, das ist ... falsch. 'myTest.one.write (3)' überschreibt 'm_flags' auf' 3', ohne dass eine Verschiebung stattfindet. Ist es "Übung dem Leser überlassen"? Weil es keine Möglichkeit gibt, den korrekten Offset mit dieser Methode implizit abzuleiten –

+0

@MooingDuck yeah, wie ich in der Frage sagte, fügte ich das Bitshifting nicht hinzu. Es ist etwas, das Sie bereits wissen, wie ich es tun musste, um nur die Struktur in dem Beispiel zu haben um es klarer zu machen. Jedes Feld hat INDEX als Vorlageparameter, so dass read() und write() bereits Zugriff auf das Bit haben, mit dem sie arbeiten sollen.Es ist nur eine Frage der Bitmasken und Operationen mit INDEX, um das gewünschte Bit zu isolieren. – qeadz

1

Erstens, wenn Sie eine solche Wirkung erzielen wollen:

int main() { 
    Test t; 
    cout << t.one.read() << endl; 
    t.one.write(1); 
    cout << t.one.read() << endl; 
} 

Sie müssen informieren one und two, dass sie die flags manipulieren - so die erste Änderung - hinzugefügt flags als Argument FIELD:

struct Test { 
    Flags<> flags; 
    FIELD(one,   0, 1, flags); 
//       ^^^^^          
    FIELD(two, AFTER(one), 2, flags); 
//   ^^^^^^^^^^ 
}; 

Andere Änderung ist, dass ich Ihre Art der Anwendung der vorherigen Flagge als index zur nächsten Flagge geändert - siehe Verwendung von AFTER Makro. Ich glaube, es ist jetzt besser lesbar und vereinfacht meinen Vorschlag. AFTER werde nachher vorgestellt, lass uns zuerst die wichtigere Magie besprechen.

Also, ich eingeführt, um die FieldManip Klasse, die gegebenen Fahnen zu manipulieren:

template <typename Flags, int i, int s> 
class FieldManip { 
public: 
    constexpr static const Field field = {i, s}; 

    FieldManip(Flags* flags) : flags(flags) {} 
    auto read() 
    { 
     return flags->readField(field); 
    } 
    template <typename T> 
    void write(T value) 
    { 
     flags->writeField(field, value); 
    } 
private: 
    Flags* flags; 
}; 

Es den zusätzlichen Speicher der Größe kostet: sizeof(Flags*), aber guter Compiler ist jede CPU-Overhead zu optimieren. Ich fürchte, es gibt keine andere Möglichkeit, diese zusätzlichen Speicher als die Zahlung, wenn Sie Ihre Anforderung entspannen - so one und two konnte die Mitgliederfunktionen, nicht Membervariablen ...

So ist die Definition neuer FLAGS ist einfach:

#define FIELD(name, i, s, flags) \ 
    FieldManip<decltype(flags), i, s> name{&flags} 

Es ist Zeit AFTER zu erklären:

Zuerst einige Vereinfachung der Field, extra Konstruktor entfernt ein nd hinzugefügt after Standalone-Funktion:

class Field { 
public: 
    constexpr Field(int i, int s) : index(i), size(s) {} 
    int index, size; 
}; 
constexpr int after(const Field& prev) { return prev.index + prev.size; } 

Aber in Makro haben wir FieldManip nicht Field - so nächste Funktion benötigt:

template <typename FieldManip> 
constexpr int after() { return after(FieldManip::field); } 

Und die AFTER:

#define AFTER(fieldmanip) after<decltype(fieldmanip)>() 
+0

"Es kostet den zusätzlichen Speicher der Größe: sizeof (Flags *)" - das ist ziemlich viel Aufwand, und ziemlich genau den Zweck des Packens von Daten dicht mit Bitfeldern besiegt. –

+0

@dgtech Ist eine solche Verwendung akzeptabel: 't.one.read (t.flags, 1); t.one.write (t.flags) '? Es ist machbar, aber nicht so viel anders als 't.flags.readField (t.one) ...' :( – PiotrNycz

+0

@ dgtech Ich würde Lösung von Srdjan.veljkovic verwenden ... Sieht am cleversten aus und kostet nichts .. . – PiotrNycz

2

Mit absolut keine Ahnung, was Ihre Absicht ist, mein erster Vorschlag ist einfach zu verwenden ein Bitfeld. Es ist tausendmal einfacher/schneller/etc.

struct Test { 
    unsigned long long one : 1; 
    unsigned long long one : 2; 
}; 

Wenn Sie jedoch wirklich eine Klasse möchten, habe ich eine FieldReference-Klasse erstellt, die in etwa dem entspricht, was Sie tun.

Klasse:

#include <cassert> 
#include <type_traits> 
#include <cstddef> 

template<class T, size_t offset_, size_t size_> 
struct FieldReference { 
    static const size_t offset = offset_; 
    static const size_t size = size_; 
    static const size_t mask = ~T(((~0)<<offset<<size)|((1<<offset)-1)); 

    explicit FieldReference(T& f) :flags(&f) {} 
    operator T() const {return (flags[0]&mask)>>offset;} 
    FieldReference& operator=(T v) { 
     assert((v&~(mask>>offset))==0); 
     flags[0] &= ~mask; 
     flags[0] |= (v<<offset); 
     return *this; 
    } 
private: 
    T* flags; 
}; 
#define FIRSTFIELD(Flags,Name,Size) \ 
    auto Name() -> FieldReference<decltype(Flags),0,Size> {return FieldReference<decltype(Flags),0,Size>(Flags);} \ 
    FieldReference<std::add_const<decltype(Flags)>::type,0,Size> Name() const {return FieldReference<std::add_const<decltype(Flags)>::type,0,Size>(Flags);} 
#define FIELD(Flags,Name,Previous,Size) \ 
    auto Name() -> FieldReference<decltype(Flags),decltype(Previous())::offset,Size> {return FieldReference<decltype(Flags),decltype(Previous())::offset,Size>(Flags);} \ 
    auto Name() const -> FieldReference<std::add_const<decltype(Flags)>::type,decltype(this->Previous())::offset,Size> {return FieldReference<std::add_const<decltype(Flags)>::type,decltype(Previous())::offset,Size>(Flags);} 

Verbrauch:

struct Test { 
    unsigned long long flags = 0; 
    FIRSTFIELD(flags,one,1); 
    FIELD(flags,two,one,2); 
}; 

#include <iostream> 

int main() { 
    Test t; 
    t.one() = 1; //That seems less verbose 
    t.two() = 3; 
    std::cout << t.two(); 
    return 0; 
} 

http://coliru.stacked-crooked.com/a/c027d9829ce05119

Die Felder jeden Raum nehmen Sie nicht auch immer, außer während Sie auf ihnen arbeiten, und auch dann nur sie nehmen Sie den Abstand eines einzelnen Zeigers. Alle Offsets, Größen und Masken werden zur Kompilierungszeit berechnet, also sollte dies schneller als Ihr Code sein.

+0

'operator T' ist nicht 'constexpr' anders als OPs Lösung.Wenn Sie einfach Ihren ctor vollständig in' FieldReference' (und die Idee der Privatsphäre) fallen lassen, können Sie '{return {& flags}'. – Yakk

+0

@Yakk: Ich gebe zu Ich habe "conexpr" noch nicht wirklich studiert, da ich MSVC benutze, das es nicht hat. Aber ich denke, dass mein Code genau so "constexpr" wie die OP-Version ist. Mein 'operator T()' entspricht seinem 'readField' –

+0

Dir fehlt das Schlüsselwort "constexpr", also ist deins eindeutig nicht "constexpr".;) – Yakk

2

Hier ist, wie Sie wirklich nah an was Sie wollen. Bitte denken Sie daran, dass Sie gesagt haben, dass die Implementierung hässlich sein kann. :)

template<class T, T mask, T bitpos> 
class Field { 
    T &d_t; 
public: 
    Field(T &t) : d_t(t) {} 
    T read() const { 
    return (d_t & mask) >> bitpos; 
    } 
    void write(T const &t) { 
    d_t = (d_t & ~mask) | (t << bitpos); 
    } 
}; 


#define BTFDENUMDECL1(name, width) name##start 
#define BTFDENUMDECL2(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL1(__VA_ARGS__) 
#define BTFDENUMDECL3(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL2(__VA_ARGS__) 
#define BTFDENUMDECL4(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL3(__VA_ARGS__) 

#define BTFNMEMBER1(field, name, width) auto name() {   \ 
    return Field<decltype(field), ((1 << width) - 1) << name##start, name##start>(field); } 

#define BTFNMEMBER2(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER1(field, __VA_ARGS__) 
#define BTFNMEMBER3(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER2(field, __VA_ARGS__) 
#define BTFNMEMBER4(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER3(field, __VA_ARGS__) 

#define GET_MACRO(_1,_1_,_2,_2_,_3,_3_,_4,_4_, NAME, ...) NAME 

#define BITFIELDS(field, ...)      \ 
    private: uint32_t field;      \ 
    enum E##flags { GET_MACRO(__VA_ARGS__, BTFDENUMDECL4,0, BTFDENUMDECL3,0, BTFDENUMDECL2,0, BTFDENUMDECL1,0)(__VA_ARGS__) }; \ 
public:         \ 
GET_MACRO(__VA_ARGS__, BTFNMEMBER4,0, BTFNMEMBER3,0, BTFNMEMBER2,0, BTFNMEMBER1,0)(field, __VA_ARGS__) 

Dies ist für C++ 14, könnte aber zu C++ 11 "reduziert" werden. Es unterstützt bis zu 4 Felder, aber Sie können einfach mehr hinzufügen mit einfachen Klonen von BTFDENUMDECL und BTFNMEMBER Makros und Aktualisierung GET_MACRO. Sie würden es gerne nutzen:

struct Test { 
    BITFIELDS(flags, 
    one, 1, 
    two, 2, 
    three, 3 
    ); 
}; 

und im Code:

Test test; 
test.one().write(0); 
std::cout << test.one().read() << std::endl; 

So gibt es nur die Klammern auf die gewünschte Syntax hinzugefügt.

Eine kurze Erklärung: Wir verwenden (ob) die variable Anzahl von Argumenten für Makros. Wir nehmen zwei Parameter (aus der Variablenargumente "list") gleichzeitig - die meisten Beispiele, die Sie finden, nehmen einen Parameter auf einmal, also wird dies ein wenig ungewöhnlich sein. Im Makro BITFIELDS definieren wir zunächst eine Enum mit den Bitpositionen der Felder und dann eine Funktion für jedes Feld. Funktionen für Felder geben einen Proxy mit den Methoden read() und write() zurück, der die in der Enumeration definierten Bitpositionen verwendet.

Dies ist die nackte Implementierung. Sie können Ihre eigenen Schnickschnack hinzufügen, z. B. Überprüfung für Bereich in write()), ein anderes Makro für einen Typ für das Feld (BITFIELDST(T, field, ...)), etc.

Für C++ 98, müssten Sie es umgestalten a wenig (um die Verwendung von __VA_ARGS__ zu entfernen), und verwenden Sie es, indem Sie die Anzahl der Parameter im Namen des Makros "call" angeben, wie BITFIELDS4.

1

Warum Methoden aufrufen?

Zunächst eine etwas andere Flaggen.

template<class C, size_t N=0, class D=uint32_t> 
struct Flags { 
    Flags(Flags const&)=default; 
    Flags():data() {} // remember to zero! 
    Flags(D raw):data(raw) {} 

    template<unsigned start, unsigned width> 
    constexpr void set(D bits) { 
    D m = mask<start, width>(); 
    data &= ~m; 
    data |= (bits<<start)&m; 
    } 
    template<unsigned start, unsigned width> 
    constexpr D get(D bits) const { 
    D m = mask<start, width>(); 
    return (data&m)>>start; 
    } 
private: 
    template<unsigned start, unsigned width> 
    static constexpr D mask() { 
    return ((1<<(width)-1)<<start; 
    } 
    D data; 
}; 

Unsere Flags nun von der Art des Behälters eingegeben wird es in ist

Wenn Sie mehr als einen Satz von Flags in einem Behälter möchten, einen Index für n übergeben.

namespace details { 
    template<class T> struct tag{using type=T;}; 

    template<class C, size_t n, unsigned start, unsigned width> 
    struct field {}; 

    template<class Flags, class Field> 
    struct pseudo_ref { 
    Flags& flags; 
    template<class U> 
    constexpr pseudo_ref operator=(U&& u)const{ 
     field_assign(flags, Field{}, std::forward<U>(u)); 
     return *this; 
    } 
    template<class T> 
    constexpr operator T()const{ 
     return field_get(tag<T>{}, flags, Field{}); 
    } 
    }; 

    template<class C, size_t n, class D, unsigned start, unsigned width> 
    constexpr auto operator*(
    Flags<C,n,D>& flags, field<C, n, start, width> 
) 
    -> psuedo_ref<Flags<C,n,D>, field<C,n,start,width>> 
    { 
    return {flags}; 
    } 
    template<class C, size_t n, class D, unsigned start, unsigned width> 
    constexpr auto operator*(
    Flags<C,n,D> const& flags, field<C,n,start,width> 
) 
    -> psuedo_ref<Flags<C,n,D> const, field<C,n,start,width>> 
    { 
    return {flags}; 
    } 
    template<class C, size_t n, class D, unsigned start, unsigned width, class U> 
    void field_assign(Flags<C,n,D>& flags, field<C,n,start,width>, U&& u){ 
    flags.set<start, width>(std::forward<U>(u)); 
    } 
    template<class T,class C, size_t n, class D, unsigned start, unsigned width> 
    constexpr T field_get(
    tag<T>, Flags<C,n,D> const& flags, field<C,n,start,width> 
) { 
    return flags.get<start,width>(); 
    } 
    template<class F> 
    struct field_end; 
    template<class C, size_t n, unsigned start, unsigned width> 
    struct field_end:std::integral_constant<unsigned, start+width>{}; 
} 
template<class C, unsigned width, size_t n=0> 
using first_field = field<C,n,0,width>; 
template<class C, class F, unsigned width, size_t n=0> 
using next_field = field<C,n,details::field_end<F>::value,width>; 

jetzt die Syntax sieht wie folgt aus:

struct Test { 
    Flags<Test> flags; 
}; 
first_field<Test, 1> one; 
next_field<Test, decltype(one), 2> two; 

Test t; 
uint32_t o = t*one; 
t*one = 1; 

und alles wird automatisch an den Bit Hantieren Code geschickt.

Beachten Sie den vollständigen Mangel an Makros. Sie könnten einen verwenden, um decltype oben zu entfernen.

+0

Haben Sie diese Lösung getestet? Ich finde ein paar Fehler, und außerdem ist die Wahl des Operators "*" nicht gerade aufregend, wenn es um Lesbarkeit und Klarheit der Absicht geht. –

0

Ich weiß, dass übermäßig komplex und kaum auszumalen C++ 11 und C++ 14-Features sind das, was trendy ist, aber hier ist eine schnelle und schmutzige Lösung mit bösen Makros:

#define GETMASK(index, size) (((1 << size) - 1) << index) 
#define READFROM(data, index, size) ((data & GETMASK(index, size)) >> index) 
#define WRITETO(data, index, size, value) (data = (data & (~GETMASK(index, size))) | (value << index)) 
#define FIELD(data, name, index, size) \ 
    inline decltype(data) name() { return READFROM(data, index, size); } \ 
    inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); } 

Und dann einfach:

struct Test { 
    uint flags; 
    FIELD(flags, one, 0, 1) 
    FIELD(flags, two, 1, 2) 
}; 

t.set_two(3); 
cout << t.two(); 

es erzeugt Accessoren für die Felder, als ob sie Eigenschaften des Objekts enthalten sind, ist es weniger ausführlich ist, und IMO sehr lesbar da auch, dass der gemeinsame Weg Menschen Objekteigenschaften aus gekapselten Daten belichten ist.

Nachteile - Sie müssen den Feldindex selbst berechnen, aber Sie können den Ansatz mit dem Generieren von Zugriffsmethoden zusammen mit Ihrer vorhandenen Implementierung auf statischen Feldern mit Konstruktoren verwenden, um dies zu vermeiden.

Upsides - es ist kurz, einfach, effizient und rückwärtskompatibel - ändern die decltype-typeof und es wird funktionieren mit Pre-C++ 11 und sogar einfach C.