2013-04-08 11 views
20

Come è possibile sovraccaricare l'operatore |= su un valore di tipo (in C++ 11, GCC) fortemente codificato?Come sovraccaricare | = operatore su enumerato ambito?

Voglio testare, impostare e cancellare i bit su enum fortemente digitati. Perché fortemente digitato? Perché i miei libri dicono che è una buona pratica. Ma questo significa che devo static_cast<int> ovunque. Per evitare ciò, sovraccarico gli operatori | e &, ma non riesco a capire come sovraccaricare l'operatore |=su un enum. Per una lezione, devi semplicemente inserire the operator definition in the class, ma per le enumerazioni che non sembrano funzionare sintatticamente.

Questo è quello che ho finora:

enum class NumericType 
{ 
    None     = 0, 

    PadWithZero    = 0x01, 
    NegativeSign   = 0x02, 
    PositiveSign   = 0x04, 
    SpacePrefix    = 0x08 
}; 

inline NumericType operator |(NumericType a, NumericType b) 
{ 
    return static_cast<NumericType>(static_cast<int>(a) | static_cast<int>(b)); 
} 

inline NumericType operator &(NumericType a, NumericType b) 
{ 
    return static_cast<NumericType>(static_cast<int>(a) & static_cast<int>(b)); 
} 

La ragione per cui faccio questo: questo è il modo in cui funziona nel fortemente tipizzato C#: un enum c'è solo una struttura con un campo del suo sottostante tipo e un gruppo di costanti definito su di esso. Ma può avere qualsiasi valore intero che si adatti al campo nascosto dell'enumerazione.

E sembra che le enumerazioni C++ funzionino nello stesso modo. In entrambe le lingue, i cast devono passare da enum a int o viceversa. Tuttavia, in C# gli operatori bit a bit sono sovraccaricati per impostazione predefinita e in C++ non lo sono.

+5

Non sono sicuro che questo abbia senso. I singoli _bit_ sono enumerati, ma 'PadWithZero | NegativeSign = 0x03' che è _non_ una costante enumerata valida. – Useless

+1

@Useless Sì, è solo un esempio basato su una versione C++ 11 di una sequenza di NUMERICTYPE_ definisce trovata in Linux 0.1 usata per implementare 'printf'. Il risultato deve essere un membro dell'enumerazione originale? Vengo da uno sfondo C# e le enfasi con scope previste si comportano come quelle in C#. – Virtlink

+3

Sto dicendo che il tuo tipo enumerato non è chiuso sotto '|', quindi non ha senso costringerlo in un (valore illegale di) quel tipo. Enumera i valori di _flag constant_, ma lascia che una _combination di flags_ sia un int. – Useless

risposta

23
inline NumericType& operator |=(NumericType& a, NumericType b) 
{ 
    return a= a |b; 
} 

Questo funziona? Compile and run: (Ideone)

#include <iostream> 
using namespace std; 

enum class NumericType 
{ 
    None     = 0, 

    PadWithZero    = 0x01, 
    NegativeSign   = 0x02, 
    PositiveSign   = 0x04, 
    SpacePrefix    = 0x08 
}; 

inline NumericType operator |(NumericType a, NumericType b) 
{ 
    return static_cast<NumericType>(static_cast<int>(a) | static_cast<int>(b)); 
} 

inline NumericType operator &(NumericType a, NumericType b) 
{ 
    return static_cast<NumericType>(static_cast<int>(a) & static_cast<int>(b)); 
} 

inline NumericType& operator |=(NumericType& a, NumericType b) 
{ 
    return a= a |b; 
} 

int main() { 
    // your code goes here 
    NumericType a=NumericType::PadWithZero; 
    a|=NumericType::NegativeSign; 
    cout << static_cast<int>(a) ; 
    return 0; 
} 

stampa 3.

+1

No, questo comportamento non è definito perché stai restituendo una variabile locale per riferimento. –

+7

No: _Riferimento sulla variabile locale 'a' restituito_ – Virtlink

+0

Ora non funzionerà per i provini ... –

1

Combinando i valori distinti per fare nuove, valori non definiti, si sono totalmente in contraddizione con la paradigma forte tipizzazione.

Sembra che si stiano impostando singoli bit di bandiera completamente indipendenti. In questo caso, non ha senso combinare i bit in un tipo di dati in cui tale combinazione produce un valore non definito.

si dovrebbe decidere sulla dimensione dei dati di bandiera (char, short, long, long long) and roll con esso. È possibile, tuttavia, utilizzare tipi specifici per testare, flag impostati e chiari:

typedef enum 
{ 
    PadWithZero    = 0x01, 
    NegativeSign   = 0x02, 
    PositiveSign   = 0x04, 
    SpacePrefix    = 0x08 
} Flag; 

typedef short Flags; 

void SetFlag(Flags & flags, Flag f) 
{ 
    flags |= static_cast<Flags>(f); 
} 

void ClearFlag(Flags & flags, Flag f) 
{ 
    flags &= ~static_cast<Flags>(f); 
} 

bool TestFlag(const Flags flags, Flag f) 
{ 
    return (flags & static_cast<Flags>)(f)) == static_cast<Flags>(f); 
} 

Questo è molto semplice, e va bene quando ogni bandiera è solo un singolo bit. Per le bandiere mascherate, è un po 'più complesso. Ci sono modi per incapsulare i bit flag in una classe fortemente tipizzata, ma ne vale davvero la pena. Nel tuo caso, non sono convinto che lo sia.

+1

Il motivo per cui lo faccio: questo è il modo in cui funziona in C# fortemente tipizzato: un enum c'è solo una struttura con un campo del suo tipo sottostante, e un gruppo di costanti definite su di esso. Ma può avere qualsiasi valore intero che si adatti al campo nascosto dell'enumerazione. E sembra che le enumerazioni C++ funzionino nello stesso modo. In entrambe le lingue, i cast devono passare da enum a int o viceversa. Tuttavia, in C# gli operatori bit a bit sono sovraccaricati per impostazione predefinita e in C++ non lo sono. A proposito ... 'typedef enum {} Flag' non è la sintassi C++ 11 per enumerare:' enum class Flag {} '. – Virtlink

+1

Solo perché le enumerazioni sembrano funzionare in questo modo, non rende giusto abusare del tipo sottostante. La tipizzazione forte esiste specificamente per impedirti di farlo. – paddy

+0

Ovviamente non hai programmato in C#. : P Loro non lo chiamano _abuse_, Microsoft [lo consiglia] (http://msdn.microsoft.com/en-us/library/vstudio/cc138362.aspx). Mi piace l'idea di attenersi al tipo di enum, anche per i bit di bit, invece di generalizzare tutto a int. Il C++ lo fa già troppo. – Virtlink

1

This seems to work for me:

NumericType operator |= (NumericType &a, NumericType b) { 
    unsigned ai = static_cast<unsigned>(a); 
    unsigned bi = static_cast<unsigned>(b); 
    ai |= bi; 
    return a = static_cast<NumericType>(ai); 
} 

Tuttavia, si può ancora considerare la definizione di una classe per la vostra collezione di enum bit:

class NumericTypeFlags { 
    unsigned flags_; 
public: 
    NumericTypeFlags() : flags_(0) {} 
    NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {} 
    //...define your "bitwise" test/set operations 
}; 

Quindi, modificare le | e & operatori a restituire NumericTypeFlags invece.

+0

La tua classe ha un campo interno nascosto, proprio come un enum. Ma non ha le sue costanti, quindi non appena incontri un metodo che si aspetta un 'NumericTypeFlags' non ottieni alcun aiuto dal tuo ambiente di sviluppo. – Virtlink

+1

@Virtlink: fornisce una migliore sicurezza del tipo rispetto a un semplice 'unsigned'. Ha una semantica migliore rispetto alla creazione di un 'NumericType' con un valore non nel' enum'. L'implementazione dell'interfaccia può garantire che solo gli argomenti compatibili con 'NumericType' vengano utilizzati per test e impostazioni. In breve, l'aiuto va all'utente della classe, a costo di qualche lavoro dall'implementatore per renderlo utile all'utente. – jxh

+0

@Virtlink: [Un'implementazione di esempio.] (Http://ideone.com/JhNzFA) – jxh

Problemi correlati