2012-03-27 9 views
9

何百ものメッセージタイプを持つ何らかのプロトコルがあるとしましょう。各メッセージタイプはC++クラスでモデル化したいと考えています。「注釈付き」フィールドを持つクラスを設計するにはどうすればよいですか?

std::tuple<int, double, char> message; 

print(message); // the usual variadic magic 

これはよく、すべての罰金とある:各クラスは自動的に各フィールドを処理することができるはずですので、自然な解決策は、必要なすべての種類のstd::tupleをちょうど持っていることです。しかし、今では各フィールドに名前を付けたいので、コード内のフィールドを参照するときにその名前を使用できるようにしたいと思っています。単純に、またはCで、私が書かれている場合があります:

struct Message 
{ 
    int header; 
    double temperature; 
    char flag; 
}; 

我々はタプルの再帰的なオートマジックの処理能力を失うが、私たちは文字通り、各フィールドに名前を付けることができますこの方法を。 C++では、我々は、列挙型を用いて、両方の操作を行うことができます

struct Message 
{ 
    enum FieldID { header, temperature, flag }; 
    static const char * FieldNames[] = { "header", "temperature", "flag" }; 

    typedef std::tuple<int, double, char> tuple_type; 

    template <FieldID I> 
    typename std::tuple_element<I, tuple_type>::type & get() 
    { return std::get<I>(data); } 

    template <FieldID I> 
    static const char * name() { return FieldNames[I]; } 

    tuple_type data; 
}; 

今、私は、Message m; m.get<Message::header>() = 12;などを言うことができる、と私はフィールド上で再帰し、自分の名前が付け、自分の価値を各プリントアウトすることができます


など

今の質問: はどのように私は繰り返しなしで、効率的なコードを作成できますか?どのような方法がある外部生成ツールを必要とせずに、このような何かを達成するために、プリプロセッサ、ブーストおよびC++ 11を、組み合わせ

START_MESSAGE(Message) 
ADDFIELD(int, header) 
ADDFIELD(double, temperature) 
ADDFIELD(char, flag) 
END_MESSAGE 

理想的には、私がこれを言うことができるようにしたいです? (私はBoost.Preprocessorがこの "水平"と "垂直"繰り返しを呼んでいると思います。フィールドデータを何とか "転置"する必要があります)。ここで重要な点は、情報を繰り返す必要がなく、 1つのフィールドには1つの変更のみが必要です。

+1

をこの種の問題、シンプルな記述言語とカスタムプリプロセッサのインクルードファイルを生成します長期的には最高です。 「ソースファイル」は簡単に保守可能で、プロジェクトが必要とする場合は外部ツールで生成することもできます。 –

+0

@AlexandreC .:任意の段階で、「もう少し小さなツール」は常により良い答えのように見えます。しかし、物事の壮大な計画では、あなたはあなたと一緒に持ち運び、文書化し、人々を覚えて訓練するもう一つのことがあります。箱から出てきたものを持っていることは、恐るべきマクロを設定するのに苦労することは間違いありません。 –

答えて

3

これは、ブーストのプリプロセッサシーケンスで行うことができます。

#define CREATE_MESSAGE(NAME, SEQ) ... 

CREATE_MESSAGE(SomeMessage, 
    (int)(header) 
    (double)(temperature) 
    (char)(flag) 
) 

定義を生成するには、各ペアを反復処理する必要があります。面白いのであれば、私はおそらくいくつかの手配をすることができますが、私は便利なサンプルコードはありません。

私はフィールドのすべてのシリアル化を生成するこのような何かのためのジェネレータを持っていました。私はちょっと遠すぎたような気がします。私は具体的な定義のように感じており、フィールド上の宣言的な訪問者はより単純です。他の誰かが私の後にコードを保持しなければならない場合には少し不思議です。私はあなたが明らかに状況にあるのか分からない。それを実装した直後にはまだ予約があった。 :)

私はチャンスがなかったが、C++ 11の機能をもう一度見てみるとクールだ。

更新:

はまだ出て動作するようにいくつかのねじれがありますが、これは主に取り組んでいます。

#include <boost/preprocessor.hpp> 
#include <boost/preprocessor/seq/for_each_i.hpp> 
#include <boost/preprocessor/arithmetic/mod.hpp> 
#include <boost/preprocessor/control/if.hpp> 

#include <tuple> 

#define PRIV_CR_FIELDS(r, data, i, elem) \ 
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),elem BOOST_PP_COMMA,BOOST_PP_EMPTY)() 

#define PRIV_CR_STRINGS(r, data, i, elem) \ 
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_STRINGIZE(elem) BOOST_PP_COMMA,BOOST_P 

#define PRIV_CR_TYPES(r, data, i, elem) \ 
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_EMPTY,elem BOOST_PP_COMMA)() 

#define CREATE_MESSAGE(NAME, SEQ) \ 
    struct NAME { \ 
     enum FieldID { \ 
      BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_FIELDS, _, SEQ) \ 
     }; \ 
     std::tuple< \ 
      BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_TYPES, _, SEQ) \ 
     > data;\ 
     template <FieldID I> \ 
      auto get() -> decltype(std::get<I>(data)) { \ 
       return std::get<I>(data); \ 
      } \ 
     template <FieldID I> \ 
      static const char * name() { \ 
       static constexpr char *FieldNames[] = { \ 
        BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_STRINGS, _, SEQ) \ 
       }; \ 
       return FieldNames[I]; \ 
      } \ 
    }; 

CREATE_MESSAGE(foo, 
     (int)(a) 
     (float)(b) 
    ) 

#undef CREATE_MESSAGE 

int main(int argc, char ** argv) { 

    foo f; 
    f.get<foo::a>() = 12; 

    return 0; 
} 

getのdecltypeに問題があります。私は本当にそこに何を期待するかを知るためにタプルを使用していません。私は、型やフィールドの生成方法とは関係ないと思います。ここで

は、プリプロセッサは-Eで生産されたものである。

struct foo { 
    enum FieldID { a , b , }; 
    std::tuple< int , float , > data; 
    template <FieldID I> 
    auto get() -> decltype(std::get<I>(data)) { 
     return std::get<I>(data); 
    } 
    template <FieldID I> static const char * name() { 
    static constexpr char *FieldNames[] = { "a" , "b" , }; 
    return FieldNames[I]; 
    } 
}; 
+0

これは有望ですね。私はその文書を読むことができます。より完全なコード例があれば、私は非常に感謝しています(そして、多額の奨励金を送るでしょう)。 –

+0

@KerrekSBここでは、より強い型付けされた列挙型を作成しようとしている人のためのリンク(ブースト・ボールト)への参照があります。 http://stackoverflow.com/a/439004/839436 –

+0

@KerrekSB更新されました。これまで完全には機能していませんが、構造体フィールドが生成されているようです。 –

1

これは答えではなく、単に別の(恐ろしい)考え方です。私は一度書いたinlファイルを持っています。それはちょっとそっくりそのまま漠然と似ています。それはここにある:

#define BITNAME color 
#define BITTYPES SEPERATOR(Red) SEPERATOR(Green) SEPERATOR(Blue) 
#define BITTYPE unsigned char 
#include "BitField.inl" 

inlファイルがSEPERATORを再定義し、再度BITTYPESを使用して、名前付きメンバーを持つカスタムビットフィールドタイプを作成します。http://ideone.com/6CvgR

基本的なコンセプトは、呼び出し側がこれを行います。これは、ToString機能を含む、簡単に使用できます。

colorBitfield Pixel; 
Pixel.BitField = 0; // sets all values to zero; 
Pixel.Green = 1; // activates green; 
std::cout << "Pixel.Bitfield=" << (int)Pixel.BitField << std::endl; //this is machine dependant, probably 2 (010). 
Pixel.BitField |= (colorBitfield::GreenFlag | colorBitfield::BlueFlag); // enables Green and Blue 
std::cout << "BlueFlag=" << (Pixel.BitField & colorBitfield::BlueFlag) << std::endl; // 1, true. 
std::cout << "sizeof(colorBitField)=" << sizeof(colorBitfield) << std::endl; 

インラインファイル自体は恐ろしいコードですが、このように曖昧にアプローチすると、呼び出し元の使用が簡単になることがあります。

私は時間がある場合、私はあなたが欲しいもののためにこのアイデアに沿って何かを作ることができるかどうかを確認します。

0

あなたは(Boost.Serializationライブラリーから)何BOOST_SERIALIZATION_NVPに似た何かをすることができません。マクロは、引数の名前と値を結びつける(短命の)ラッパー構造を作成します。この名前と値のペアは、ライブラリコードによって処理されます(名前は実際にはXMLシリアル化でのみ重要です。そうでない場合は破棄されます)。

だから、あなたのコードは次のようになります。トム・カーの提案に基づいて

int header  = 42; 
double temperature = 36.6; 
char flag  = '+'; 
print (Message() + MY_NVP (header) + MY_NVP (temperature) + MY_NVP (flag)); 
+0

うーん、興味深い...私はブーストかどうか疑問に思っていた。シリアライゼーションには仕事に役立つツールがあるかもしれません。しかし、実際には、実際の 'Message'クラスが欲しいです。私はすでにシリアル化コードを持っているので、インスタンス化して、シリアライザに送信したり、その内容をログファイルに出力したりすることができます。 –

+0

@KerrekSB:私はあなたが誤解していたと思います。 'Message'クラスを今のままにしておきたいと思いますか?*同様のクラスを簡単に作成できるようにしますか(' Message1'、... 'Message53')? – doublep

+0

はい、確かに。私は多くのメッセージクラスを(永久に)設計したいと思っています。これらのクラスをすべて*簡単に作成する方法があります。私は例のように手動で各自書くことができますが、それは面倒で恐ろしいことになります。私はクラス定義を作成するための外部ツールを書くこともできますが、それも非常に不満足です。 –

1

、私はBoost.Preprocessorシーケンスを見上げました。全体gcc -std=c++11 -E -Pとの事(および再フォーマット)をコンパイル

#include <boost/preprocessor/seq.hpp> 
#include <boost/preprocessor/comma_if.hpp> 
#include <boost/preprocessor/arithmetic.hpp> 
#include <boost/preprocessor/stringize.hpp> 

#include <tuple> 

#define PROJECT1(a,b) a 
#define PROJECT2(a,b) b 

#define BOOST_TT_projectqu(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) BOOST_PP_STRINGIZE(PROJECT2 t) 
#define BOOST_TT_project1(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) PROJECT1 t 
#define BOOST_TT_project2(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) PROJECT2 t 


template <typename T> struct Field { }; 

#define MESSAGE(classname, data) struct classname            \ 
    {                        \ 
     typedef std::tuple<BOOST_PP_SEQ_FOR_EACH(BOOST_TT_project1, ~, data)> tuple_type;   \ 
                           \ 
     static constexpr char const * FieldNames[BOOST_PP_SEQ_SIZE(data)] = { BOOST_PP_SEQ_FOR_EACH(BOOST_TT_projectqu, ~, data) }; \ 
                           \ 
     enum FieldID { BOOST_PP_SEQ_FOR_EACH(BOOST_TT_project2, ~, data) };      \ 
                           \ 
     template <FieldID I> using type = typename std::tuple_element<I, tuple_type>::type;  \ 
                           \ 
     template <FieldID I> typename std::tuple_element<I, tuple_type>::type & get() { return std::get<I>(dat); } \ 
     template <FieldID I> typename std::tuple_element<I, tuple_type>::type const & get() const { return std::get<I>(dat); } \ 
                           \ 
    private:                      \ 
     tuple_type dat;                   \ 
    }; 

MESSAGE(message,   \ 
    ((int, header))   \ 
    ((double,temperature)) \ 
    ((char, flag))   \ 
) 

できます:ここで私が思いついたものだ

template <typename T> struct Field { }; 

struct message { 
    typedef std::tuple< int , double , char > tuple_type; 
    static constexpr char const * FieldNames[3] = { "header" , "temperature" , "flag" }; 
    enum FieldID { header , temperature , flag }; 
    template <FieldID I> using type = typename std::tuple_element<I, tuple_type>::type; 
    template <FieldID I> typename std::tuple_element<I, tuple_type>::type & get() { return std::get<I>(dat); } 
    template <FieldID I> typename std::tuple_element<I, tuple_type>::type const & get() const { return std::get<I>(dat); } 
    private: tuple_type dat; }; 
関連する問題