C++预处理器:避免代码重复成员变量列表

pap*_*jam 60 c++ templates arguments c-preprocessor

我有一个多个类,每个类都有不同的成员变量,这些变量在构造函数中被简单地初始化.这是一个例子:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    const char *name;
    int age;
};
Run Code Online (Sandbox Code Playgroud)

每个都有一个相关的print<>()功能.

template <>
void print<Person>(const Person &person)
{
    std::cout << "name=" << name << "\n";
    std::cout << "age=" << age << "\n";
}
Run Code Online (Sandbox Code Playgroud)

由于参数列表在四个位置复制,因此该代码容易出错.如何重写代码以避免重复?我想使用预处理器和/或模板.

例如,我可以使用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
Run Code Online (Sandbox Code Playgroud)

或者更好的是,基于模板的方法?

请不要质疑我为什么要这样做,有合理的设计决策导致多个具有命名参数的类似对象.出于性能原因,参数需要被命名为成员变量.我只是在探索是否可以只列出一次参数及其类型.

Pau*_* II 199

您需要做的是让预处理器生成有关字段的反射数据.此数据可以存储为嵌套类.

首先,为了使它在预处理器中更容易和更清晰,我们将使用类型化表达式.类型化表达式只是将类型放在括号中的表达式.所以不写作,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
Run Code Online (Sandbox Code Playgroud)

接下来,我们定义一个REFLECTABLE宏来生成关于每个字段的数据(加上字段本身).这个宏将被调用如下:

REFLECTABLE
(
    (const char *) name,
    (int) age
)
Run Code Online (Sandbox Code Playgroud)

因此,使用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)); \
    } \
}; \
Run Code Online (Sandbox Code Playgroud)

这样做是生成一个常量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;
    };
};
Run Code Online (Sandbox Code Playgroud)

现在迭代字段我们使用访问者模式.我们创建一个从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));
}
Run Code Online (Sandbox Code Playgroud)

现在我们把它放在一起.以下是我们如何定义Person类:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};
Run Code Online (Sandbox Code Playgroud)

这是广义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());
}
Run Code Online (Sandbox Code Playgroud)

一个例子:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

哪个输出:

name=Tom
age=82
Run Code Online (Sandbox Code Playgroud)

瞧,我们刚刚用100行代码实现了C++中的反射.

  • +1:这太可怕了,太可怕了.我喜欢! (43认同)
  • 这应该赢得某种奖励,以获得stackoverflow的最佳答案!这里非常令人印象深刻的宏伏都教. (20认同)
  • 我不得不将TYPEOF宏的实现更改为BOOST_PP_SEQ_HEAD(x),以便使用VS2012进行编译.正如所写的,它导致了像make_const <const char*,name,>这样的东西 (3认同)
  • @Hippiehunter是的`TYPEOF`宏需要一个C99预处理器,尽管有一些解决方法可以让它在msvc中工作.对于msvc,使用`BOOST_PP_SEQ_HEAD`是一个简单而有效的解决方法,但如果类型中包含逗号,则它在C99预处理器中的工作方式不同. (2认同)

byt*_*ter 6

我已经解决了我的通用结构与JSON代码相同的问题.

定义一个宏:REFLECT(CLASS_NAME,MEMBER_SEQUENCE)其中MEMBER_SEQUENCE是(姓名)(年龄)(其他)(...)

将REFLECT扩展为类似于:

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

您可以使用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) );
}
Run Code Online (Sandbox Code Playgroud)

http://bytemaster.github.com/mace/group_ mace _reflect__typeinfo.html

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