For a small project I'm working on, I've been looking for an iterable enum
-like class in C++, since neither C-style nor scoped enum
s seem to support iteration in a way that isn't hack-y. I stumbled across this (relatively) old article, from which I've drawn inspiration to create a robust Enum
class supporting iteration and C++11 features.
The client simply creates a DEnum
class, which includes the desired enum
items as static const members, inheriting from this Enum
class. This prevents code using DEnum
from creating new items. Local object copies of the enum
items can be created through the default copy and copy-assignment ctors. I've also added a function that allows creation of a local subset of the enum
items, which also supports iteration (including using range-for syntax). The class should be type-safe in preventing type conversion of its members and instantiated copies.
I'm just looking for any tips on how I could implement this better. Does this code follow C++11 best practices? Are there any problems I've overlooked?
// enum.h
#ifndef ENUM_H_
#define ENUM_H_
#include <algorithm>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <set>
template <class T>
class Enum {
private:
// Comparator used to determine enum item ordering
struct EnumLTComparator {
bool operator() (const T* e1, const T* e2) {
return e1->value() < e2->value();
}
};
public:
typedef std::set<const T*, EnumLTComparator> ItemsSet;
typedef typename ItemsSet::size_type set_size_type;
typedef typename ItemsSet::const_iterator const_iterator;
typedef typename ItemsSet::const_reverse_iterator const_r_iterator;
int value() const { return value_; }
static set_size_type size() { return items_.size(); }
// Iteration
static const_iterator begin() { return items_.cbegin(); }
static const_iterator end() { return items_.cend(); }
static const_r_iterator rbegin() { return items_.crbegin(); }
static const_r_iterator rend() { return items_.crend(); }
bool operator== (T e2) const {return this->value() == e2.value();}
// Return a set which contains pointers to any number of unique enum items
static ItemsSet Subset(std::initializer_list<T> items) {
ItemsSet subset;
for (auto i: items) {
subset.insert(CorrespondingEnum(i.value()));
}
return subset;
}
protected:
// Constructors
explicit Enum(int value): value_(value) {
// Warn clients if they try to add multiple items with the same value
if (IsValidValue(value)) {
std::cerr << "The value \'" << value << "\' has been assigned to "
<< "multiple items in this enum. All duplicates are omitted "
<< "from the set." << std::endl;
return;
}
// This cast seems ugly, since the object being constructed by the
// inheriting class will already be of type T, but I don't see any
// way around this.
items_.insert(static_cast<T*>(this));
}
// Compiler-generated copy and copy-assign ctors generate local copies of
// the static enum items
private:
static const T* CorrespondingEnum(int value) {
auto EnumPredicateCorresponds = [value](const T* elem) -> bool {
return elem->value() == value;
};
const_iterator it = find_if(items_.begin(),
items_.end(),
EnumPredicateCorresponds);
return (it != items_.end()) ? *it : nullptr;
}
static bool IsValidValue(int value) {
return CorrespondingEnum(value) != nullptr;
}
int value_;
static ItemsSet items_;
};
#endif
And a client would use this class as follows:
// color.h
#ifndef COLOR_H_
#define COLOR_H_
#include "enum.h"
class Color: public Enum<Color> {
public:
using Enum::Enum; //Color::Color(int) is still protected
static const Color blue;
static const Color green;
static const Color red;
static const Color white;
static const Color yellow;
static const Color multicolor;
static const Color hidden;
};
#endif
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// color.cc
#include "color.h"
#include "enum.h"
// I would prefer not to require this, but I don't see a way around it
template<>
typename Enum<Color>::ItemsSet Enum<Color>::items_{};
const Color Color::blue(0);
const Color Color::green(1);
const Color Color::red(2);
const Color Color::white(3);
const Color Color::yellow(4);
const Color Color::multicolor(5);
const Color Color::hidden(6);
Finally, some testing with cout:
#include <iostream>
#include "color.h"
using std::cout;
using std::endl;
int main() {
cout << Color::size() << endl; //7
for (auto iCol = Color::begin(); iCol != Color::end(); ++iCol) {
cout << (*iCol)->value() << endl;
}
/* 0
* 1
* 2
* 3
* 4
* 5
* 7
*/
cout << (**(Color::begin()) == Color::blue) << endl; //1
cout << (**(Color::begin()) == Color::red) << endl; //0
cout << (**(Color::rbegin()) == Color::hidden) << endl; //1
cout << (**(Color::rbegin()) == Color::yellow) << endl; //0
auto subset = Color::Subset({Color::blue, Color::red});
cout << (**(subset.begin()) == Color::blue) << endl; //1
cout << (**(subset.rbegin()) == Color::red) << endl; //0
for (auto iCol: subset) {
cout << iCol->value() << endl;
// 0
// 2
}
//Color mauve(2); //Error: Color::Color is protected
//Color *col = &(Color::red); //Error: can't convert from 'const Color*'
// to Color*'
}
-
\$\begingroup\$ codereview.stackexchange.com/questions/14309/… \$\endgroup\$Loki Astari– Loki Astari2014年07月22日 00:14:25 +00:00Commented Jul 22, 2014 at 0:14
1 Answer 1
You are no longer using the enum properties.
const Color Color::blue(0);
const Color Color::green(1);
const Color Color::red(2);
const Color Color::white(3);
const Color Color::yellow(4);
const Color Color::multicolor(5);
const Color Color::hidden(6);
But rather a set of const values that have been put in a set. Sort of defeats the purpose of enum in my mind.
I would just create a class for iterating across enums.
This is what I would have done.
#include <iostream>
#include <vector>
#include <iterator>
template<typename T, T... args>
struct EnumIter : public std::iterator<std::input_iterator_tag, T,ptrdiff_t,const T*,const T&>
{
static constexpr T values[] = {args...};
static constexpr std::size_t size = sizeof...(args);
int pos;
EnumIter() // No value is end
: pos(size)
{}
EnumIter(T val)
: pos(std::distance(&values[0], std::find(&values[0], &values[size], val)))
{}
const T& operator*() const {return values[pos];}
EnumIter& operator++() {++pos;return *this;}
EnumIter operator++(int) {EnumIter r(*this);this->operator++();return r;}
bool operator==(EnumIter const& rhs) {return pos == rhs.pos;}
bool operator!=(EnumIter const& rhs) {return pos != rhs.pos;}
};
template<typename T, T... args>
constexpr T EnumIter<T, args...>::values[];
Usage:
enum Ace { One = 101, Two = 233, Three = 455};
typedef struct EnumIter<Ace, One, Two, Three> AceIter;
int main()
{
for(AceIter loop(One); loop != AceIter(); ++loop)
{
std::cout << static_cast<int>(*loop) << "\n";
}
}
Run
> g++ -std=c++11 enum.cpp
> ./a.out
101
233
455
Combine this with a previous printing of enum done here Conversion between enum and string in C++ class header
-
\$\begingroup\$ Shouldn't
Ace
be a strongly-typedenum
? It seems a little ugly to have casting done in the main loop. \$\endgroup\$Jamal– Jamal2014年07月22日 16:21:56 +00:00Commented Jul 22, 2014 at 16:21 -
\$\begingroup\$ @Jamal: casting is a different problem. And solved here: codereview.stackexchange.com/questions/14309/… \$\endgroup\$Loki Astari– Loki Astari2014年07月22日 16:25:49 +00:00Commented Jul 22, 2014 at 16:25
-
\$\begingroup\$ Have not practiced
strongly-typed
enum's yet. So I don't know. \$\endgroup\$Loki Astari– Loki Astari2014年07月22日 16:26:32 +00:00Commented Jul 22, 2014 at 16:26 -
\$\begingroup\$ Okay. I don't quite know if they'll clear that up by themselves (I've mostly noticed the existence of the old
enum
s), but I do know that the new ones aren't forced to becomeint
s. \$\endgroup\$Jamal– Jamal2014年07月22日 16:28:33 +00:00Commented Jul 22, 2014 at 16:28 -
2\$\begingroup\$ Can you expand on how this "defeats the purpose of enums in [your] mind"? The end result is a strong type which can take on a finite number of symbolic values, which seems to me like what
enum
s accomplish. \$\endgroup\$Libster– Libster2014年07月22日 22:01:41 +00:00Commented Jul 22, 2014 at 22:01