web-dev-qa-db-ja.com

C ++プリプロセッサ:メンバー変数リストのコードの繰り返しを避ける

コンストラクターで簡単に初期化される異なるメンバー変数を持つ複数のクラスがあります。以下に例を示します。

_struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    const char *name;
    int age;
};
_

それぞれに関連するprint<>()関数があります。

_template <>
void print<Person>(const Person &person)
{
    std::cout << "name=" << name << "\n";
    std::cout << "age=" << age << "\n";
}
_

パラメーターリストは4か所で複製されるため、このコードはエラーを起こしやすいです。この重複を避けるために、どのようにコードを書き直せますか?プリプロセッサやテンプレートを使用したいと思います。

たとえば、X-argsプリプロセッサ技術を使用できますか?

_#define ARGUMENTS \
    ARG(const char *, name) \
    ARG(int, age)

struct Person
{
    Person(LIST_TYPE_NAME_COMMA(ARGUMENTS))
       :
       LIST_NAME_INIT(ARGUMENTS)
    {
    }
private:
    LIST_TYPE_NAME_SEMICOLON(ARGUMENTS)
};

template <>
void print<Person>(const Person &person)
{
   LIST_COUT_LINE(ARGUMENTS)
}

#undef ARGUMENTS
_

それとも、テンプレートベースのアプローチですか?

なぜ私がこれをしたいのか疑問に思わないでください。名前付きパラメータを持つ複数の同様のオブジェクトをもたらす合理的な設計決定があります。パフォーマンス上の理由から、パラメーターにはメンバー変数の名前を付ける必要があります。パラメーターとそのタイプを一度だけリストできるかどうかを調査しています。

59
paperjam

必要なのは、プリプロセッサにフィールドに関するリフレクションデータを生成させることです。このデータは、ネストされたクラスとして保存できます。

最初に、プリプロセッサでの記述を簡単かつ簡潔にするために、型付き式を使用します。型付き式は、型を括弧で囲んだ単なる式です。したがって、int xと書く代わりに、(int) xと書きます。型付き式を支援する便利なマクロを次に示します。

#define REM(...) __VA_ARGS__
#define EAT(...)

// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x

次に、REFLECTABLEマクロを定義して、各フィールド(およびフィールド自体)に関するデータを生成します。このマクロは次のように呼び出されます。

REFLECTABLE
(
    (const char *) name,
    (int) age
)

Boost.PP を使用して、各引数を反復処理し、次のようなデータを生成します。

// A helper metafunction for adding const to a type
template<class M, class T>
struct make_const
{
    typedef T type;
};

template<class M, class T>
struct make_const<const M, T>
{
    typedef typename boost::add_const<T>::type type;
};


#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
    Self & self; \
    field_data(Self & self) : self(self) {} \
    \
    typename make_const<Self, TYPEOF(x)>::type & get() \
    { \
        return self.STRIP(x); \
    }\
    typename boost::add_const<TYPEOF(x)>::type & get() const \
    { \
        return self.STRIP(x); \
    }\
    const char * name() const \
    {\
        return BOOST_PP_STRINGIZE(STRIP(x)); \
    } \
}; \

これは、クラス内のリフレクト可能なフィールドの数である定数fields_nを生成します。次に、field_dataを各フィールドに特化します。また、reflectorクラスをフレンドします。これは、プライベートな場合でもフィールドにアクセスできるようにするためです。

struct reflector
{
    //Get field_data at index N
    template<int N, class T>
    static typename T::template field_data<N, T> get_field_data(T& x)
    {
        return typename T::template field_data<N, T>(x);
    }

    // Get the number of fields
    template<class T>
    struct fields
    {
        static const int n = T::fields_n;
    };
};

次に、フィールドを反復処理するために、訪問者パターンを使用します。 0からフィールド数までのMPL範囲を作成し、そのインデックスのフィールドデータにアクセスします。次に、フィールドデータをユーザー提供の訪問者に渡します。

struct field_visitor
{
    template<class C, class Visitor, class T>
    void operator()(C& c, Visitor v, T)
    {
        v(reflector::get_field_data<T::value>(c));
    }
};


template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
    typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
    boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}

真実の瞬間のために、私たちはそれをすべてまとめました。 Personクラスを定義する方法は次のとおりです。

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};

一般的なprint_fields関数は次のとおりです。

struct print_visitor
{
    template<class FieldData>
    void operator()(FieldData f)
    {
        std::cout << f.name() << "=" << f.get() << std::endl;
    }
};

template<class T>
void print_fields(T & x)
{
    visit_each(x, print_visitor());
}

例:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

どの出力:

name=Tom
age=82

そして、ほら、100行未満のコードで、C++にリフレクションを実装しました。

197
Paul Fultz II

JSONコードの汎用構造体で同じ問題を解決しました。

マクロを定義します:REFLECT(CLASS_NAME、MEMBER_SEQUENCE)ここで、MEMBER_SEQUENCEは(name)(age)(other)(...)

REFLECTを次のようなものに展開します。

template<>
struct reflector<CLASS_NAME> {
  template<typename Visitor>
  void visit( Visitor&& v ) {
     v( "name" , &CLASS_NAME::name );
     v( "age",   &CLASS_NAME::age  );
     ... 
  }
}

BOOST_PP_SEQ_FOREACHを使用して、SEQを訪問者に展開できます。

次に、印刷ビジターを定義します。

template<typename T>
struct print_visitor {
  print_visitor( T& s ):self(s){}

  template<typename R>
  void operator( const char* name, R (T::*member) )const {
     std::cout<<name<<"= "<<self.*member<<std::endl;
  } 
  T& self;
}

template<typename T>
void print( const T& val ) {
   reflector<T>::visit( print_visitor<T>(val) );
}

http://bytemaster.github.com/mace/group_mace_reflect__typeinfo.html

https://github.com/bytemaster/mace/blob/master/libs/reflect/include/mace/reflect/reflect.hpp

6
bytemaster

この削減されたユースケースには、ソリューションが非常に最適であると思います。私たちが助けることができるのは、フィールドを反復することから恩恵を受けるprint以外の追加の関数がある場合です。

これは Boost.FusionFusion Sequences ;の完璧な例です。コンパイル時のリフレクションを導入するために使用できます。その上で、より一般的なランタイム動作を生成できます。

そのため、たとえば、Fusion.Map(各タイプの1回の出現に制限されます)または他のそのような空想を使用して要素を宣言できます。

タイプがFusion Sequenceに準拠していない場合(または内部に手を加えたくない場合)、 adapted セクションに などのアダプターがありますBOOST_FUSION_ADAPT_STRUCT 。そしてもちろん、すべてがstruct(またはパブリックメンバーを持つ)ではないため、クラスのより一般的なバージョンもあり、すぐに見苦しくなります: BOOST_FUSION_ADAPT_ADT

クイックスタート から盗む:

struct print_xml {
    template <typename T>
    void operator()(T const& x) const {
        std::cout
            << '<' << typeid(x).name() << '>'
            << x
            << "</" << typeid(x).name() << '>'
            ;
    }
};

int main() {
    vector<int, char, std::string> stuff(1, 'x', "howdy");
    int i = at_c<0>(stuff);
    char ch = at_c<1>(stuff);
    std::string s = at_c<2>(stuff);

    for_each(stuff, print_xml());
}

アダプタを使用すると、型を「適応」できるため、次のようになります。

struct Foo { int bar; char const* buzz; };

BOOST_FUSION_ADAPT_STRUCT(
    Foo,
    (int, bar)
    (char const*, buzz)
)

その後:

int main() {
    Foo foo{1, "Hello");
    for_each(foo, print_xml());
}

それはかなり印象的なライブラリです:)

4
Matthieu M.

なぜプリプロセッサを使用する必要があるのですか? boost.fusionライブラリの紹介には、ユースケースに多少似た例があります。

2
zvrba

クラスではなく、タプルが必要です。これにより、プリプロセッサハッカーに頼ることなく、すべての問題を簡単に解決できます。

1
Puppy

ポールの偉大なREFLECTABLEマクロへの追加としての2セントです。継承階層を適切に処理するには、フィールドの空のリスト、つまりREFLECTABLE()が必要でした。次の変更がこのケースを処理します。

// http://stackoverflow.com/a/2831966/2725810
#define REFLECTABLE_0(...)                                                     \
    static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__);           \
    friend struct reflector;                                                   \
    template <int N, class Self> struct field_data {};                         \
    BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data,                                \
                            BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

#define REFLECTABLE_1(...)                                                     \
    static const int fields_n = 0;

#define REFLECTABLE_CONST2(b, ...) REFLECTABLE_##b(__VA_ARGS__)

#define REFLECTABLE_CONST(b, ...) REFLECTABLE_CONST2(b,__VA_ARGS__)


#define REFLECTABLE(...)                                                      \
    REFLECTABLE_CONST(BOOST_PP_IS_EMPTY(__VA_ARGS__), __VA_ARGS__) 
1
AlwaysLearning