C ++では、列挙型(ランタイムまたはコンパイル時(推奨))を列挙して、反復ごとに関数を呼び出したり、コードを生成したりすることはできますか?
ユースケースの例:
enum abc
{
start
a,
b,
c,
end
}
for each (__enum__member__ in abc)
{
function_call(__enum__member__);
}
もっともらしい重複:
C ++では、列挙型(ランタイムまたはコンパイル時(推奨))を列挙して、反復ごとに関数を呼び出したり、コードを生成したりすることはできますか?
ユースケースの例:
enum abc
{
start
a,
b,
c,
end
}
for each (__enum__member__ in abc)
{
function_call(__enum__member__);
}
もっともらしい重複:
@StackedCrooked の回答に追加するには、をオーバーロードoperator++
し、イテレータのような機能を持たせることができます。operator--
operator*
enum Color {
Color_Begin,
Color_Red = Color_Begin,
Color_Orange,
Color_Yellow,
Color_Green,
Color_Blue,
Color_Indigo,
Color_Violet,
Color_End
};
namespace std {
template<>
struct iterator_traits<Color> {
typedef Color value_type;
typedef int difference_type;
typedef Color *pointer;
typedef Color &reference;
typedef std::bidirectional_iterator_tag
iterator_category;
};
}
Color &operator++(Color &c) {
assert(c != Color_End);
c = static_cast<Color>(c + 1);
return c;
}
Color operator++(Color &c, int) {
assert(c != Color_End);
++c;
return static_cast<Color>(c - 1);
}
Color &operator--(Color &c) {
assert(c != Color_Begin);
return c = static_cast<Color>(c - 1);
}
Color operator--(Color &c, int) {
assert(c != Color_Begin);
--c;
return static_cast<Color>(c + 1);
}
Color operator*(Color c) {
assert(c != Color_End);
return c;
}
<algorithm>
いくつかのテンプレートでテストしてみましょう
void print(Color c) {
std::cout << c << std::endl;
}
int main() {
std::for_each(Color_Begin, Color_End, &print);
}
現在、Color
一定の双方向イテレータです。これは、上記で手動でコーディングした再利用可能なクラスです。より多くの列挙型で機能する可能性があることに気付いたので、同じコードを何度も繰り返すのは非常に面倒です
// Code for testing enum_iterator
// --------------------------------
namespace color_test {
enum Color {
Color_Begin,
Color_Red = Color_Begin,
Color_Orange,
Color_Yellow,
Color_Green,
Color_Blue,
Color_Indigo,
Color_Violet,
Color_End
};
Color begin(enum_identity<Color>) {
return Color_Begin;
}
Color end(enum_identity<Color>) {
return Color_End;
}
}
void print(color_test::Color c) {
std::cout << c << std::endl;
}
int main() {
enum_iterator<color_test::Color> b = color_test::Color_Begin, e;
while(b != e)
print(*b++);
}
実装は次のとおりです。
template<typename T>
struct enum_identity {
typedef T type;
};
namespace details {
void begin();
void end();
}
template<typename Enum>
struct enum_iterator
: std::iterator<std::bidirectional_iterator_tag,
Enum> {
enum_iterator():c(end()) { }
enum_iterator(Enum c):c(c) {
assert(c >= begin() && c <= end());
}
enum_iterator &operator=(Enum c) {
assert(c >= begin() && c <= end());
this->c = c;
return *this;
}
static Enum begin() {
using details::begin; // re-enable ADL
return begin(enum_identity<Enum>());
}
static Enum end() {
using details::end; // re-enable ADL
return end(enum_identity<Enum>());
}
enum_iterator &operator++() {
assert(c != end() && "incrementing past end?");
c = static_cast<Enum>(c + 1);
return *this;
}
enum_iterator operator++(int) {
assert(c != end() && "incrementing past end?");
enum_iterator cpy(*this);
++*this;
return cpy;
}
enum_iterator &operator--() {
assert(c != begin() && "decrementing beyond begin?");
c = static_cast<Enum>(c - 1);
return *this;
}
enum_iterator operator--(int) {
assert(c != begin() && "decrementing beyond begin?");
enum_iterator cpy(*this);
--*this;
return cpy;
}
Enum operator*() {
assert(c != end() && "cannot dereference end iterator");
return c;
}
Enum get_enum() const {
return c;
}
private:
Enum c;
};
template<typename Enum>
bool operator==(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
return e1.get_enum() == e2.get_enum();
}
template<typename Enum>
bool operator!=(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
return !(e1 == e2);
}
C ++は現在、列挙子の反復を提供していません。それにもかかわらず、これが必要になることがあります。一般的な回避策は、開始と終了をマークする値を追加することです。例えば:
enum Color
{
Color_Begin,
Color_Red = Color_Begin,
Color_Orange,
Color_Yellow,
Color_Green,
Color_Blue,
Color_Indigo,
Color_Violet,
Color_End
};
void foo(Color c)
{
}
void iterateColors()
{
for (size_t colorIdx = Color_Begin; colorIdx != Color_End; ++colorIdx)
{
foo(static_cast<Color>(colorIdx));
}
}
少しの肉体労働なしではどちらも不可能です。あなたがその領域を掘り下げても構わないと思っているなら、多くの仕事はマクロによって行うことができます。
Konrad の言うことを拡張すると、「反復ごとにコードを生成する」場合の 1 つの可能なイディオムは、インクルード ファイルを使用して列挙を表すことです。
mystuff.h:
#ifndef LAST_ENUM_ELEMENT
#define LAST_ENUM_ELEMENT(ARG) ENUM_ELEMENT(ARG)
#endif
ENUM_ELEMENT(foo)
ENUM_ELEMENT(bar)
LAST_ENUM_ELEMENT(baz)
// not essential, but most likely every "caller" should do it anyway...
#undef LAST_ENUM_ELEMENT
#undef ENUM_ELEMENT
enum.h:
// include guard goes here (but mystuff.h doesn't have one)
enum element {
#define ENUM_ELEMENT(ARG) ARG,
#define LAST_ENUM_ELEMENT(ARG) ARG
#include "mystuff.h"
}
main.cpp:
#include "enum.h"
#define ENUM_ELEMENT(ARG) void do_##ARG();
#include "mystuff.h"
element value = getValue();
switch(value) {
#define ENUM_ELEMENT(ARG) case ARG: do_##ARG(); break;
#include "mystuff.h"
default: std::terminate();
}
そこで、新しい要素「qux」を追加するには、それを mystuff.h に追加してdo_qux
関数を記述します。ディスパッチコードに触れる必要はありません。
もちろん、列挙型の値が連続しない特定の整数である必要がある場合は、列挙型の定義とENUM_ELEMENT(foo)
... リストを別々に維持することになり、面倒です。
これは私にはハッキーに思えますが、あなたの目的に合うかもしれません:
enum Blah {
FOO,
BAR,
NUM_BLAHS
};
// later on
for (int i = 0; i < NUM_BLAHS; ++i) {
switch (i) {
case FOO:
// foo stuff
break;
case BAR:
// bar stuff
break;
default:
// you're missing a case statement
}
}
特別な開始値が必要な場合は、それを定数にして列挙型に設定できます。私はこれがコンパイルされるかどうかをチェックしませんでしたが、そこに近いはずです:-)。お役に立てれば。
このアプローチは、ユースケースにとって適切なバランスになると思います。さまざまな列挙型の束に対してこれを行う必要がなく、プリプロセッサのものを処理したくない場合に使用します。コメントして、おそらくTODOを追加して、後日、より良いものに変更してください:-)。
ただし、反復を使用して列挙型のような機能を実装する独自のクラスを定義することはできます。「タイプセーフな列挙型デザインパターン」と呼ばれる、1.5Java以前の時代のトリックを思い出すかもしれません。C++と同等のことを行うことができます。
テンプレートが大好きですが、上記のいずれかで迷子にならないように、将来/他の人が使用するためにこれをメモしておきます。
列挙型は、既知の順序付けられた方法で物事を比較するために便利です。これらは通常、整数値に対する読みやすさのために関数にハードコーディングされて使用されます。リテラルに置き換えられず、実行時に保持およびアクセスされることを除いて、プリプロセッサ定義に多少似ています。
HTML エラー コードを定義する列挙型があり、500 番台のエラー コードがサーバー エラーであることがわかっている場合は、次のように読みやすくなります。
enum HtmlCodes {CONTINUE_CODE=100,CLIENT_ERROR=400,SERVER_ERROR=500,NON_STANDARD=600};
if(errorCode >= SERVER_ERROR && errorCode < NON_STANDARD)
よりも
if(errorCode >= 500 && errorCode < 600)
重要な部分はこれです。それらは配列に似ています! ただし、 整数値をキャスト するために使用されます。
短い例:
enum Suit {Diamonds, Hearts, Clubs, Spades};
//does something with values in the enum past "Hearts" in this case
for(int i=0;i<4;i++){
//Could also use i or Hearts, because the enum will turns these both back into an int
if( (Suit)(i) > 1 )
{
//Whatever we'd like to do with (Suit)(i)
}
}
多くの場合、列挙型は char* 配列または文字列配列でも使用されるため、関連付けられた値でメッセージを出力できます。通常、それらは次のように、列挙型に同じ値のセットを持つ単なる配列です。
char* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"};
//Getting a little redundant
cout << Suits[Clubs] << endl;
//We might want to add this to the above
//cout << Suits[(Suit)(i)] << endl;
もちろん、上記の回答のように、列挙型の反復を処理するジェネリック クラスを作成する方が良いでしょう。
TMP を使用して、提案されているランタイム手法の一部を静的に実行できます。
#include <iostream>
enum abc
{
a,
b,
c,
end
};
void function_call(abc val)
{
std::cout << val << std::endl;
}
template<abc val>
struct iterator_t
{
static void run()
{
function_call(val);
iterator_t<static_cast<abc>(val + 1)>::run();
}
};
template<>
struct iterator_t<end>
{
static void run()
{
}
};
int main()
{
iterator_t<a>::run();
return 0;
}
このプログラムの出力は次のとおりです。
0
1
2
この手法の適切な扱いについては、Abrahams の Ch 1、Gurtovoy の「C++ Template Metaprogramming」を参照してください。提案されている実行時手法よりもこの方法で行う利点は、このコードを最適化すると、静的をインライン化できることであり、次のものとほぼ同等です。
function_call(a);
function_call(b);
function_call(c);
インラインの function_call により、コンパイラからさらに多くのヘルプが得られます。
ここでは、他の列挙反復手法に対する同じ批判が当てはまります。この手法は、列挙が 1 から最後まで連続して増加する場合にのみ機能します。