Gibt es eine generische Möglichkeit, int
in enum
in C++
umzuwandeln?
Wenn int
in den Bereich eines enum
fällt, sollte es einen enum
-Wert zurückgeben, andernfalls werfen Sie ein exception
aus. Gibt es eine Möglichkeit, es generisch zu schreiben? Es sollte mehr als ein enum type
unterstützt werden.
Hintergrund: Ich habe einen externen enum type und no control über den Quellcode. Ich möchte diesen Wert in einer Datenbank speichern und abrufen.
Das Offensichtliche ist, Ihr Enum zu kommentieren:
// generic code
#include <algorithm>
template <typename T>
struct enum_traits {};
template<typename T, size_t N>
T *endof(T (&ra)[N]) {
return ra + N;
}
template<typename T, typename ValType>
T check(ValType v) {
typedef enum_traits<T> traits;
const T *first = traits::enumerators;
const T *last = endof(traits::enumerators);
if (traits::sorted) { // probably premature optimization
if (std::binary_search(first, last, v)) return T(v);
} else if (std::find(first, last, v) != last) {
return T(v);
}
throw "exception";
}
// "enhanced" definition of enum
enum e {
x = 1,
y = 4,
z = 10,
};
template<>
struct enum_traits<e> {
static const e enumerators[];
static const bool sorted = true;
};
// must appear in only one TU,
// so if the above is in a header then it will need the array size
const e enum_traits<e>::enumerators[] = {x, y, z};
// usage
int main() {
e good = check<e>(1);
e bad = check<e>(2);
}
Das Array muss mit e
auf dem neuesten Stand gehalten werden. Dies ist ein Ärgernis, wenn Sie nicht der Autor von e
sind. Wie Sjoerd sagt, kann es wahrscheinlich mit jedem anständigen Build-System automatisiert werden.
In jedem Fall haben Sie es mit 7.2/6 zu tun:
Für eine Aufzählung, wobei emin der .__ ist. kleinster enumerator und emax ist der am größten sind die Werte der Aufzählung sind die Werte des zugrunde liegenden Typs im Bereich von bmin bis bmax, wobei bmin und bmax sind jeweils die kleinste und größte Werte von kleinstes Bitfeld, in dem emin gespeichert werden kann und emax. Es ist möglich, ein .__ zu definieren. Aufzählung, deren Werte nicht .__ sind. von einem seiner Enumeratoren definiert.
Wenn Sie also nicht der Autor von e
sind, haben Sie möglicherweise keine Garantie, dass gültige Werte von e
tatsächlich in der Definition erscheinen.
Hässlich.
enum MyEnum { one = 1, two = 2 };
MyEnum to_enum(int n)
{
switch( n )
{
case 1 : return one;
case 2 : return two;
}
throw something();
}
Nun zur richtigen Frage. Warum brauchst du das? Der Code ist hässlich, nicht leicht zu schreiben (*?) Und nicht einfach zu warten und nicht leicht in Ihren Code zu integrieren. Der Code, der Ihnen sagt, dass es falsch ist. Warum bekämpfen?
BEARBEITEN:
Alternativ, vorausgesetzt, dass Aufzählungen ganzzahlige Typen in C++ sind:
enum my_enum_val = static_cast<MyEnum>(my_int_val);
aber das ist noch hässlicher als oben, viel fehleranfälliger, und es wird nicht werfen, wie Sie wollen.
Wenn sich die Werte, wie Sie beschrieben haben, in einer Datenbank befinden, warum sollten Sie nicht einen Codegenerator schreiben, der diese Tabelle liest und eine .h- und .cpp-Datei mit der Enum-Funktion und der Funktion to_enum(int)
erstellt.
Vorteile:
to_string(my_enum)
-Funktion hinzufügen.Was denkst du über dieses?
#include <iostream>
#include <stdexcept>
#include <set>
#include <string>
using namespace std;
template<typename T>
class Enum
{
public:
static void insert(int value)
{
_set.insert(value);
}
static T buildFrom(int value)
{
if (_set.find(value) != _set.end()) {
T retval;
retval.assign(value);
return retval;
}
throw std::runtime_error("unexpected value");
}
operator int() const { return _value; }
private:
void assign(int value)
{
_value = value;
}
int _value;
static std::set<int> _set;
};
template<typename T> std::set<int> Enum<T>::_set;
class Apples: public Enum<Apples> {};
class Oranges: public Enum<Oranges> {};
class Proxy
{
public:
Proxy(int value): _value(value) {}
template<typename T>
operator T()
{
T theEnum;
return theEnum.buildFrom(_value);
}
int _value;
};
Proxy convert(int value)
{
return Proxy(value);
}
int main()
{
Apples::insert(4);
Apples::insert(8);
Apples a = convert(4); // works
std::cout << a << std::endl; // prints 4
try {
Apples b = convert(9); // throws
}
catch (std::exception const& e) {
std::cout << e.what() << std::endl; // prints "unexpected value"
}
try {
Oranges b = convert(4); // also throws
}
catch (std::exception const& e) {
std::cout << e.what() << std::endl; // prints "unexpected value"
}
}
Sie können dann den von mir veröffentlichten Code here verwenden, um die Werte zu aktivieren.
Nein, es gibt keine Introspektion in C++ und auch keine eingebaute "Domain Check" -Funktion.
Wenn Sie bereit sind, Ihre Aufzählungswerte als Vorlagenparameter aufzulisten, können Sie dies in C++ 11 mit varadic-Vorlagen tun. Sie können dies als eine gute Sache betrachten, die es Ihnen ermöglicht, Teilmengen der gültigen Aufzählungswerte in verschiedenen Kontexten zu akzeptieren. häufig nützlich, wenn Codes aus externen Quellen analysiert werden.
Vielleicht nicht ganz so allgemein, wie Sie möchten, aber der Prüfcode selbst ist verallgemeinert. Sie müssen nur die Menge der Werte angeben. Dieser Ansatz behandelt Lücken, willkürliche Werte usw.
template<typename EnumType, EnumType... Values> class EnumCheck;
template<typename EnumType> class EnumCheck<EnumType>
{
public:
template<typename IntType>
static bool constexpr is_value(IntType) { return false; }
};
template<typename EnumType, EnumType V, EnumType... Next>
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...>
{
using super = EnumCheck<EnumType, Next...>;
public:
template<typename IntType>
static bool constexpr is_value(IntType v)
{
return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
}
EnumType convert(IntType v)
{
if (!is_value(v)) throw std::runtime_error("Enum value out of range");
return static_cast<EnumType>(v);
};
enum class Test {
A = 1,
C = 3,
E = 5
};
using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>;
void check_value(int v)
{
if (TestCheck::is_value(v))
printf("%d is OK\n", v);
else
printf("%d is not OK\n", v);
}
int main()
{
for (int i = 0; i < 10; ++i)
check_value(i);
}
Sie sollten nicht möchten, dass das, was Sie beschreiben, existiert, ich befürchte, es gibt Probleme in Ihrem Code-Design.
Sie gehen auch davon aus, dass die Aufzählungen in einem Bereich liegen, aber das ist nicht immer der Fall:
enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
Dies liegt nicht in einem Bereich: Selbst wenn es möglich wäre, sollten Sie jede ganze Zahl von 0 bis 2 ^ n überprüfen, um zu sehen, ob sie mit dem Wert einiger Enummen übereinstimmen?
C++ 0x Alternative zur "hässlichen" Version, ermöglicht mehrere Enumerationen. Verwendet Initializer-Listen statt Schalter, ein bisschen sauberer IMO. Leider funktioniert das nicht, wenn Sie die Aufzählungswerte hart codieren müssen.
#include <cassert> // assert
namespace // unnamed namespace
{
enum class e1 { value_1 = 1, value_2 = 2 };
enum class e2 { value_3 = 3, value_4 = 4 };
template <typename T>
int valid_enum( const int val, const T& vec )
{
for ( const auto item : vec )
if ( static_cast<int>( item ) == val ) return val;
throw std::exception( "invalid enum value!" ); // throw something useful here
} // valid_enum
} // ns
int main()
{
// generate list of valid values
const auto e1_valid_values = { e1::value_1, e1::value_2 };
const auto e2_valid_values = { e2::value_3, e2::value_4 };
auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
assert( result1 == e1::value_1 );
auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
assert( result2 == e2::value_3 );
// test throw on invalid value
try
{
auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
assert( false );
}
catch ( ... )
{
assert( true );
}
}