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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11031062/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:45:51  来源:igfitidea点击:

C++ preprocessor: avoid code repetition of member variable list

c++templatesargumentsc-preprocessor

提问by paperjam

I have a multiple classes each with different member variables that are initialized trivially in a constructor. Here is an example:

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

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

Each has an associated print<>()function.

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

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

This code is error prone since the parameter list is replicated in four places. How can I rewrite the code to avoid this duplication? I'd like to use the preprocessor and/or templates.

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

For example, could I use the X-args preprocessor technique -- something like this?

例如,我可以使用 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

Or better, a template-based approach?

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

Please don't question why I want to do this, there are reasoned design decisions that have resulted in multiple similar objects with named parameters. The parameters need to be named member variables for performance reasons. I'm just exploring whether it's possible to list the parameters and their types only once.

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

回答by Paul Fultz II

What you need to do is have the preprocessor generate reflection data about the fields. This data can be stored as nested classes.

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

First, to make it easier and cleaner to write it in the preprocessor we will use typed expression. A typed expression is just an expression that puts the type in parenthesis. So instead of writing int xyou will write (int) x. Here are some handy macros to help with typed expressions:

首先,为了使在预处理器中编写它更容易和更清晰,我们将使用类型化表达式。类型化表达式只是将类型放在括号中的表达式。所以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

Next, we define a REFLECTABLEmacro to generate the data about each field(plus the field itself). This macro will be called like this:

接下来,我们定义一个REFLECTABLE宏来生成每个字段的数据(加上字段本身)。这个宏会被这样调用:

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

So using Boost.PPwe iterate over each argument and generate the data like this:

因此,使用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)); \
    } \
}; \

What this does is generate a constant fields_nthat is number of reflectable fields in the class. Then it specializes the field_datafor each field. It also friends the reflectorclass, this is so it can access the fields even when they are private:

这样做是生成一个常量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;
    };
};

Now to iterate over the fields we use the visitor pattern. We create an MPL range from 0 to the number of fields, and access the field data at that index. Then it passes the field data on to the user-provided visitor:

现在要遍历字段,我们使用访问者模式。我们创建一个从 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));
}

Now for the moment of truth we put it all together. Here is how we can define the Personclass:

现在到了关键时刻,我们把它们放在一起。下面是我们如何定义Person类:

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

Here is the generalized print_fieldsfunction:

这是广义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());
}

An example:

一个例子:

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

Which outputs:

哪些输出:

name=Tom
age=82

And voila, we have just implemented reflection in C++, in under 100 lines of code.

瞧,我们刚刚在 C++ 中实现了反射,不到 100 行代码。

回答by bytemaster

I have solved the same problem with my generic struct to JSON code.

我已经用我的通用结构到 JSON 代码解决了同样的问题。

Define a macro: REFLECT( CLASS_NAME, MEMBER_SEQUENCE ) where MEMBER_SEQUENCE is (name)(age)(other)(...)

定义一个宏: REFLECT( CLASS_NAME, MEMBER_SEQUENCE ) 其中 MEMBER_SEQUENCE 是 (name)(age)(other)(...)

Have REFLECT expand to something similar to:

将 REFLECT 扩展为类似于:

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

You can use BOOST_PP_SEQ_FOREACH to expand the SEQ into the visitors.

您可以使用 BOOST_PP_SEQ_FOREACH 将 SEQ 扩展到访问者中。

Then define your print visitor:

然后定义您的打印访问者:

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

http://bytemaster.github.com/mace/group_狼牙棒_reflect__typeinfo.html

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

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

回答by Matthieu M.

I am afraid that your solution is pretty optimal for this reduced usecase. Where we can help is if you have additional functions besides printthat would benefit from iterating over the fields.

恐怕您的解决方案对于这个减少的用例来说是非常理想的。我们可以提供帮助的地方是,您是否还有其他功能print可以从迭代字段中受益。

This is a perfect example for Boost.FusionFusion Sequences; they can be used to introduce compile-time reflection. On top of it you can then generate more generic runtime behavior.

这是Boost.Fusion Fusion Sequences的完美示例;它们可用于引入编译时反射。最重要的是,您可以生成更通用的运行时行为。

So, you can for example declare your elements using a Fusion.Map (which restricts you to a single occurrence of each type) or other such fantasies.

因此,例如,您可以使用 Fusion.Map(限制每种类型只出现一次)或其他此类幻想来声明您的元素。

If your type does not conform to a Fusion Sequence (or you don't want to meddle with its internals), there are adapters in the adaptedsection such as BOOST_FUSION_ADAPT_STRUCT. And of course, since not everything is a struct(or has public members), there is also a more generic version for classes, it just gets ugly soon: BOOST_FUSION_ADAPT_ADT.

如果您的类型不符合 Fusion Sequence(或者您不想干预其内部结构),则改编部分中有适配器,例如BOOST_FUSION_ADAPT_STRUCT. 当然,由于并非所有东西都是 a struct(或具有公共成员),因此类还有一个更通用的版本,它很快就会变得丑陋:BOOST_FUSION_ADAPT_ADT.

Stealing from the quick start:

快速开始窃取:

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());
}

The adapters will let you "adapt" a type, so you would get:

适配器会让你“适应”一个类型,所以你会得到:

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

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

And then:

进而:

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

It's a pretty impressive library :)

这是一个令人印象深刻的图书馆:)

回答by AlwaysLearning

Here are my 2 cents as an addition to the great REFLECTABLEmacro of Paul. I had a need to have an empty list of fields, i.e. REFLECTABLE(), to handle an inheritance hierarchy properly. The following modification handles this case:

这是我的 2 美分,作为REFLECTABLE对保罗伟大宏的补充。我需要有一个空的字段列表,即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__) 

回答by zvrba

Why do you need to use preprocessor? Introduction to boost.fusion library has an example somewhat similar to your use-case.

为什么需要使用预处理器?boost.fusion 库简介有一个与您的用例有些相似的示例。

回答by Puppy

You need a tuple, not a class. This would easily solve all of your problems without having to resort to preprocessor hackery.

你需要一个元组,而不是一个类。这将轻松解决您的所有问题,而无需求助于预处理器技巧。