Typesafe Flags
-
Tagchen Leute.
Ich habe letztens eine alte Klasse zur Verwaltung von Flags wieder rausgekramt und sie ein bisschen verfeinert.
Die Grundidee ist:
Wir brauchen Flags für viele Sachen, zB der openmode bei std::ios_base. Das Problem aber ist: ich kann einfach einen beliebigen int übergeben und es gibt keinen Compilerfehler. Das Programm fliegt mir zur Laufzeit um die Ohren.Deshalb habe ich eine Klasse geschrieben, die eine typsichere Methode für Flags anbietet. Neben meiner Implementierung kenne ich lediglich in der TTL etwas ähnliches. Wenn jemand eine bessere Implementierung oder Ansätze kennt: nur her damit.
Die TTL Version hat den Nachteil, dass man nicht mehr:
openmode mode = ios_base::out | ios_base::app; if(mode & ios_base::out) bla();schreiben kann, sondern eine neue Syntax:
openmode mode(ios_base::out, ios_base::app); if(mode.test(ios_base::out)) bla();verwenden muss.
Ich wollte aber bei der herkömmlichen Syntax bleiben. Leider brauche ich dafür ein Makro - bisher ist mir keine elegantere Lösung eingefallen

Was mir aber am wichtigsten war: die Benutzung muss genauso funktionieren wie die herkömmliche Methode mit Flags zu arbeiten, aber dennoch Typsicher sein.
Ohne Umschweife daher mal der Code
#ifndef FLAGS_HPP_INCLUDED #define FLAGS_HPP_INCLUDED #include <ostream> #define MAKE_FLAGS(ClassT) \ inline ClassT operator|(ClassT::EnumType lhs, ClassT::EnumType rhs) { \ return ClassT( \ static_cast<ClassT::BitFieldType>(lhs) \ | \ static_cast<ClassT::BitFieldType>(rhs) \ ); \ } \ \ inline ClassT operator&(ClassT::EnumType lhs, ClassT::EnumType rhs) { \ return ClassT( \ static_cast<ClassT::BitFieldType>(lhs) \ & \ static_cast<ClassT::BitFieldType>(rhs) \ ); \ } \ \ inline ClassT operator^(ClassT::EnumType lhs, ClassT::EnumType rhs) { \ return ClassT( \ static_cast<ClassT::BitFieldType>(lhs) \ ^ \ static_cast<ClassT::BitFieldType>(rhs) \ ); \ } \ \ inline ClassT operator~(ClassT::EnumType e) { \ return ClassT(~static_cast<ClassT::BitFieldType>(e)); \ } \ template<typename Parent, typename EnumT = typename Parent::EnumType, typename BitField = unsigned> class Flags : public Parent { public: typedef EnumT EnumType; typedef Parent ParentType; typedef BitField BitFieldType; private: BitFieldType value; explicit Flags(BitFieldType value) : value(value) {} public: Flags(EnumType other) : value(other) {} Flags() : value(BitFieldType()) {} BitFieldType getInternal() const { return value; } Flags operator~() { return Flags(~value); } friend Flags operator~(EnumType); friend Flags operator&(Flags const& lhs, Flags const& rhs) { return Flags(lhs.value & rhs.value); } friend Flags operator&(EnumType, EnumType); Flags const& operator&=(Flags const& other) { value&=other.value; return *this; } friend Flags operator|(Flags const& lhs, Flags const& rhs) { return Flags(lhs.value | rhs.value); } friend Flags operator|(EnumType, EnumType); Flags const& operator|=(Flags const& other) { value |= other.value; return *this; } friend Flags operator^(Flags const& lhs, Flags const& rhs) { return Flags(lhs.value ^ rhs.value); } friend Flags operator^(EnumType, EnumType); Flags const& operator^=(Flags const& other) { value ^= other.value; return *this; } friend bool operator==(Flags const& lhs, Flags const& rhs) { return lhs.value == rhs.value; } friend bool operator!=(Flags const& lhs, Flags const& rhs) { return lhs.value != rhs.value; } friend std::ostream& operator<<(std::ostream& out, Flags const& flags) { out<<flags.value; return out; } //safe bool idiom private: typedef void (Flags::*BoolType)() const; void dummy() const {} public: operator BoolType() const { return value ? &Flags::dummy : 0; } }; #endifEin kleines Verwendungsbeispiel:
#include<iostream> #include<string> #include "flags.hpp" struct WindowsFlags { enum Flags { Fullscreen = 1<<0, Modal = 1<<1, Ownerdraw = 1<<2, Border3D = 1<<3, }; }; typedef Flags<WindowsFlags, WindowsFlags::Flags> WinFlags; MAKE_FLAGS(WinFlags) void draw(WinFlags f) { if(f & WinFlags::Fullscreen) { std::cout<<"Fullscreen\n"; } else { std::cout<<"Kein Fullscren\n"; } } int main() { WinFlags f = WinFlags::Fullscreen | WinFlags::Modal; std::cout<<"f == "<<f.getInternal()<<"\n"; draw(f); if(!f) { f=WinFlags(); } std::cin.get(); }Kurze Erklärung:
typedef Flags<WindowsFlags, WindowsFlags::Flags> WinFlags;Damit definiert man den Flag-Typ. Alternativ kann man auch den 1. Parameter durch eine leere Klasse ersetzen wenn man nur ein enum ohne Klasse drumherum hat. Aber syntaktisch ist halt WinFlags::Fullscreen schöner als nur Fullscreen.
MAKE_FLAGS(WinFlags)Das definiert alle nötigen Operatoren. Das Problem ist:
Fullscreen | Modalliefert einen int defaultmäßig. Ein int stinkt aber, weil man damit keine Typsicherheit garantieren kann. Also brauchen wir eigene Operatoren für unser enum. Und diese Operatoren liefern dann immer ein "WinFlags" Objekt.
Es wäre schön wenn man um das Makro rum käme, aber im Moment sehe ich keine andere Lösung.
Der Rest ist relativ einfach: ein WinFlags Objekt verhält sich genauso wie ein int - mit der Einschränkung, dass nur typsichere Operationen erlaubt sind. Folgendes geht also nicht:
WinFlags f=7; f|=12;Folgendes dagegen schon:
WinFlags f=WinFlags::Fullscreen | WinFlags::Ownerdraw; f|=WinFlags::Modal;Teile des Codes sind schon etwas älter - also bitte nicht über die vielen teilweise unnötigen friends aufregen

Ich hoffe ihr habt Verbesserungsvorschläge und/oder findet Fehler - tobt euch aus.
-
wie wärs mit
#include <boost/type_traits/is_enum.hpp> #include <boost/utility/enable_if.hpp> template<typename T> typename boost::enable_if<boost::is_enum<T>,T>::type operator&(T lhs, T rhs) { return static_cast<T>(static_cast<int>(lhs)&static_cast<int>(rhs)); } template<typename T> typename boost::enable_if<boost::is_enum<T>,T>::type operator|(T lhs, T rhs) { return static_cast<T>(static_cast<int>(lhs)|static_cast<int>(rhs)); } template<typename T> typename boost::enable_if<boost::is_enum<T>,T>::type operator^(T lhs, T rhs) { return static_cast<T>(static_cast<int>(lhs)^static_cast<int>(rhs)); } // operator ~ ist problematisch...Irgendwie entgeht mir der Sinn des BitField-Typen. Schließlich müssen alle enums in ein int passen. Etwas problematisch ist diese evtl. Lösung, falls es bereits ein Overload für diese Operatoren fibt. Zudem ist zu überlegen, wo diese Funktionen am besten deklariert werden - im Namensraum der enums wäre ideal, aber dann hat wieder der Anwender den Aufwand. Im globalen Namensraum funktioniert möglicherweise nicht, falls es andere Überladungen gibt, die vorher gefunden werden - insgesamt ist es aber wahrscheinlich die beste Variante.
-
camper schrieb:
Irgendwie entgeht mir der Sinn des BitField-Typen.
etwaige speicher optimierung wenn man nur 4 bit oder so braucht. aber ja, das non plus ultra feature ist es nicht. bietet sich bei teamplates aber an, weil der aufwand gleich 0 ist.

Etwas problematisch ist diese evtl. Lösung, falls es bereits ein Overload für diese Operatoren fibt.
Theoretisch eine Gefahr, praktisch aber nicht.
Zudem ist zu überlegen, wo diese Funktionen am besten deklariert werden - im Namensraum der enums wäre ideal, aber dann hat wieder der Anwender den Aufwand.
Einfach das Makro MAKE_FLAG nach der Definition des enums aufrufen.
insgesamt ist es aber wahrscheinlich die beste Variante.
Das Problem ist halt, dass ich die herkömmliche Syntax haben will. Mit einer neuen Syntax wäre es kein Problem oder wenn man den Aufwand betreiben will statt enums eigene Klassen mit static membern zu benutzen.
-
könnte man das Makro nicht umgehen, indem man die Makrofunktionen zu friends der Klasse macht? Oder findet der compiler die funktionen dann net?
-
otze schrieb:
könnte man das Makro nicht umgehen, indem man die Makrofunktionen zu friends der Klasse macht? Oder findet der compiler die funktionen dann net?
Nein, findet er nicht. Ist schade, aber logisch.
-
camper schrieb:
wie wärs mit
#include <boost/type_traits/is_enum.hpp> #include <boost/utility/enable_if.hpp> template<typename T> typename boost::enable_if<boost::is_enum<T>,T>::type operator&(T lhs, T rhs) { return static_cast<T>(static_cast<int>(lhs)&static_cast<int>(rhs)); } template<typename T> typename boost::enable_if<boost::is_enum<T>,T>::type operator|(T lhs, T rhs) { return static_cast<T>(static_cast<int>(lhs)|static_cast<int>(rhs)); } template<typename T> typename boost::enable_if<boost::is_enum<T>,T>::type operator^(T lhs, T rhs) { return static_cast<T>(static_cast<int>(lhs)^static_cast<int>(rhs)); } // operator ~ ist problematisch...Ih, das stinkt, das gilt ja dann für ALLE enum Typen. Das will man aber nicht... Huiuiui

Wenn man das "boost::is_enum<T>" durch nen eigenes Template ersetzt könnte man aber zumindest das MAKRO kürzer bekommen, allerdings auf Kosten höherer Compilierzeiten.
-
Mal ein bisschen anders, ohne extra Klasse (wozu brauchen wir die überhaupt) und Macros:
#ifndef FLAGS_HPP_INCLUDED #define FLAGS_HPP_INCLUDED #include <boost/static_assert.hpp> #include <boost/typeof/typeof.hpp> #include <boost/type_traits/is_scalar.hpp> #include <boost/type_traits/is_enum.hpp> #include <boost/type_traits/is_same.hpp> #include <boost/utility/enable_if.hpp> #include <boost/mpl/bool.hpp> #include <boost/mpl/not.hpp> #include <boost/mpl/and.hpp> #include <boost/mpl/or.hpp> #include <limits> void**************************** implements_flags(...); template <typename T> struct is_flag : boost::mpl::and_< boost::is_enum< T >, boost::mpl::not_< boost::is_same< BOOST_TYPEOF_TPL(( implements_flags( *(T*)0 ) )), void**************************** > > > {}; // Gruppe 1: & ^ | &= ^= |= == != // Flag1 op Flag1 implementiert (1) // Flag1 op Scalar verboten (2) // Flag1 op Integer verboten (3) // sonst zugelassen (4) template <typename T, typename U, typename X> struct enable_if_flag : boost::enable_if< boost::mpl::or_< boost::mpl::and_< is_flag< T >, boost::is_scalar< U > >, // (1) (2) boost::mpl::and_< boost::is_scalar< T >, is_flag< U > >, // (1) (2) boost::mpl::and_< is_flag< T >, boost::mpl::bool_< std::numeric_limits< U >::is_integer > >, // (3) boost::mpl::and_< boost::mpl::bool_< std::numeric_limits< T >::is_integer >, is_flag< U > > >, // (3) X > {}; template <typename T, typename U> typename enable_if_flag< T, U, T >::type operator&(T lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return static_cast< T >( static_cast< int >( lhs ) & static_cast< int >( rhs ) ); } template <typename T, typename U> typename enable_if_flag< T, U, T >::type operator^(T lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return static_cast< T >( static_cast< int >( lhs ) ^ static_cast< int >( rhs ) ); } template <typename T, typename U> typename enable_if_flag< T, U, T >::type operator|(T lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return static_cast< T >( static_cast< int >( lhs ) | static_cast< int >( rhs ) ); } template <typename T, typename U> typename enable_if_flag< T, U, T& >::type operator&=(T& lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return lhs = static_cast< T >( static_cast< int >( lhs ) & static_cast< int >( rhs ) ); } template <typename T, typename U> typename enable_if_flag< T, U, T& >::type operator^=(T& lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return lhs = static_cast< T >( static_cast< int >( lhs ) ^ static_cast< int >( rhs ) ); } template <typename T, typename U> typename enable_if_flag< T, U, T& >::type operator|=(T& lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return lhs = static_cast< T >( static_cast< int >( lhs ) | static_cast< int >( rhs ) ); } template <typename T, typename U> typename enable_if_flag< T, U, bool >::type operator==(T lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return static_cast< int >( lhs ) == static_cast< int >( rhs ); } template <typename T, typename U> typename enable_if_flag< T, U, bool >::type operator!=(T lhs, U rhs) { BOOST_STATIC_ASSERT(( boost::is_same< T, U >::value )); return !( lhs == rhs ); } // Gruppe 2: + - * / % << >> += -= *= /= %= >>= <<= < > <= >= // Flag1 op Flag1 verboten (1) // Flag1 op Scalar verboten (2) // Flag1 op Integer verboten (3) // sonst zugelassen (4) template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator+ (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator- (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator* (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator/ (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator% (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator<< (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator>> (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator+= (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator-= (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator*= (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator/= (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator%= (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator<<=(T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator>>=(T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator< (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator> (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator<= (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } template <typename T, typename U> typename enable_if_flag< T, U, void >::type operator>= (T lhs, U rhs) { BOOST_STATIC_ASSERT( false ); } // Gruppe 3: + - ~ // op Flag verboten (1) template <typename T> typename boost::enable_if< is_flag< T > >::type operator+(T arg) { BOOST_STATIC_ASSERT( false ); } template <typename T> typename boost::enable_if< is_flag< T > >::type operator-(T arg) { BOOST_STATIC_ASSERT( false ); } template <typename T> typename boost::enable_if< is_flag< T > >::type operator~(T arg) { BOOST_STATIC_ASSERT( false ); } // verbleiben unmodifiziert: ! & #endifAnwendung dann so:
#include<iostream> #include "flags.hpp" struct WinFlags { enum type { Fullscreen = 1<<0, Modal = 1<<1, Ownerdraw = 1<<2, Border3D = 1<<3, }; friend char implements_flags(type); }; void draw(WinFlags::type f) { if(f & WinFlags::Fullscreen) { std::cout<<"Fullscreen\n"; } else { std::cout<<"Kein Fullscren\n"; } } int main() { WinFlags::type f = WinFlags::Fullscreen | WinFlags::Modal; std::cout<<"f == "<<(int)f<<"\n"; draw(f); if(!f) { f=WinFlags::type(); } std::cin.get(); }Das Enum wird kann also unmittelbar nach der Definition zum Flag gemacht werden - was sicher ein Vorteil ist: somit kann man ein Flag in einer Klasse definieren und unmittelbar in Funktionen, die inline in der Klassendefinition erscheinen, benutzen. Zudem bedeutet der Verzicht auf die Klasse Vorteile für den Compiler und Flags sind damit auch wieder PODs.
Kleine Zugabe: Man kann auf diese Weise auch mal nachträglich die ganzen ios-flags damit behandeln - ist zwar an sich undefiniert (zumal diese Flags ja gar keine enums sein müssen); aber wenn man es nur mal testweise macht, um Probleme im Code aufzuspüren, sicher nicht ganz unpraktisch.
-
Hi, kannst du mir das bitte mal genau erklären ?
//*******************************************
//* Inbound: Touritel
//* Started: 2007 Apr 18
//*******************************************PlayWave(fName)
{
WAVE_Play("Touritel/"+fName, 0, "@", "");
}main()
{
char c;CALL_Answer();
while (true) {
PlayWave("Touritel 1.wav");
DTMF_WaitForDigits(c, 2, "", 2000);
if (c == "24")
break;
}
while (true) {
PlayWave("Touritel 2.wav");
DTMF_WaitForDigits(c, 1, "", 2000);
switch (c) {
case "1" : PlayWave("Touritel 3.wav"); return 0;
case "2" : PlayWave("Touritel 4.wav"); return 0;
case "3" : PlayWave("Touritel 5.wav"); return 0;
case "4" : PlayWave("Touritel 6.wav"); return 0;
}
}
}