问题 技巧:使用宏填充数组值(代码生成)


C ++模板是伪装的宏吗?

我正在阅读上面的主题,突然想到了这个想法:为什么不尝试编写一些可以在我们的真实代码中使用的棘手宏(不仅仅是作为现实生活中无用的谜题)?

所以首先想到的是:用宏填充数组值:

int f(int &i) { return ++i; }

#define e100     r5(m20)
#define m20      m5,m5,m5,m5
#define m5       r5(e1)
#define e1       f(i)  //avoiding ++i right here, to avoid UB!
#define r5(e)    e,e,e,e,e

int main() {
        int i=0;           //this is used in the macro e1
        int a[] = {e100};  //filling array values with macros!
        int n  = sizeof(a)/sizeof(int);
        cout << "count = " << n << endl;
        for(int i = 0 ; i < n ; i++ ) 
            cout << a[i] << endl;
        return 0;
}

输出:

count = 100
1
2
3
4
.
.
.
100

在线演示: http://www.ideone.com/nUYrq

我们可以在紧凑性或通用性(可能两者)方面进一步改进这个解决方案吗?我们可以摆脱变量吗? i 我们在宏中需要哪些?还是其他任何改进?

我还想知道这是否是C ++和C中的有效代码(当然忽略了打印部分)?

编辑:

我意识到调用的顺序 f() 似乎还在 不明。我不确定,因为我认为数组初始化中的逗号是  可能与逗号运算符相同(一般情况下)。但如果确实如此,我们能否避免它以及标准的哪一部分说出来 不明


2059
2018-05-21 06:47


起源

@Marcelo:是的。在发布代码后我才意识到。但任何改进,我们可以避免吗? - Nawaz
@Nawaz:对不起,我意识到我的评论实际上是一个答案,所以我感动了。 - Marcelo Cantos
@Downvoters:为什么要downvote?是不是问题,要求改进/纠正? - Nawaz
+1 ......好问题。 - iammilind
@Nawaz - 问题也可能很糟糕。你为什么试图发明一个宏来做例如 generate() 来自<algorithm>已经可以了吗? - Bo Persson


答案:


如果你想深入研究预编程器编程,我只能推荐 Boost.Preprocessor 库作为构建块,您将避免从头开始重写事物。

例如,为了创建你的表,我会用(ideone):

#include <iostream>

#include <boost/preprocessor/repetition/enum.hpp>

#define ORDER(z, n, text) n

int main() {
  int const a[] = { BOOST_PP_ENUM(100, ORDER, ~) };
  std::size_t const n = sizeof(a)/sizeof(int);

  std::cout << "count = " << n << "\n";

  for(std::size_t i = 0 ; i != n ; ++i ) 
    std::cout << a[i] << "\n";

  return 0;
}

并把所有的遗嘱留给Boost :)

注意:这枚举从0到99,而不是1到100,还有其他操作可用于执行算术;)

编辑: 这个怎么用 ?

首先,我只能推荐doc条目 BOOST_PP_ENUM

BOOST_PP_ENUM 是一个带有3个参数的宏: (n, MACRO, data)

  • n:一个整数
  • MACRO:一个接受3个参数的宏: (z, i, data)
  • data:一些数据,方便您传递给 macro

它将被n次连续调用取代 MACRO 被逗号隔开:

MACRO(z, 0, data), MACRO(z, 1, data), ... , MACRO(z, n-1, data)

你可以随心所欲地做任何事情 MACRO

我恐怕从未使用过 z 参数,它在内部使用,理论上你可以用它来加速这个过程。


5
2018-05-21 14:16



啊,最后,我看到了一个有趣的解决方案。 100000。 - Nawaz
顺便问一下,你能解释一下这个,至少是每个部分的顶级描述吗? - Nawaz
@Nawaz:我必须承认,即使在工作中,我也很谨慎使用这个库。我个人的青睐是: BOOST_PP_ENUM_PARAM我曾经用它来为函数生成各种arities的重载(通常从0到9),因为我们仍然在工作中使用C ++ 03,并且 BOOST_PP_SEQ 集合,因为序列可以让你轻松地模拟可变参数宏。 - Matthieu M.
感谢各部分的描述。顺便说一下,doc BOOST_PP_ENUM 似乎是错的,我想你并没有打算这个链接。 - Nawaz
@Nawaz:感谢糟糕的链接,似乎他们改变了网站,现在使用i-frames ......哼:/ - Matthieu M.


P99 有一个宏,完全符合你的要求

#include "p99_map.h"

int Ara[] = { P99_POSS(100) };

它的优点是它完全是编译时间,没有功能等的动态初始化。

对于您来说,它可能具有使用C99功能的缺点,特别是具有可变长度参数的宏。


5
2018-05-21 09:16



+1。真棒。我们可以自己编写这样的宏(对于C ++)吗?怎么写的? - Nawaz
@Nawaz,不幸的是,这绝不是直截了当的。你真的需要仔细研究P99。 (或者在我的博客上进行一些解释)。许多C ++编译器都将变量参数作为扩展,因此它可以正常工作。 - Jens Gustedt


不,这不是有效的代码;行为仍未定义。由于数组初始化元素之间没有序列点,因此对f()的调用可以按任何顺序发生。

可以生成序列。 Boost.Preprocessor这样做,并使用这样的序列发出更有趣的东西。


3
2018-05-21 06:56



我认为现在“未指定”,而不是“未定义”。 - ybungalobill
@ybungalobill:我没时间调查,因此同意或不同意,但是,这听起来是正确的。 - Marcelo Cantos
@Marcelo:我认为数组初始化中的逗号与逗号运算符(通常)不同。我可能错了。标准说什么? - Nawaz
@Marcelo:你举个例子吗? Boost.Preprocessor 它产生序列。它可以填充数组值吗? - Nawaz
@Nawaz:是的,逗号运算符与数组初始化中使用的逗号不同,但两种用法都没有引入序列点。逗号运算符可以以相反的顺序自由地评估其参数,只要它“返回”最后一个参数即可。 - Marcelo Cantos


我觉得还是 template 将提供更好的解决方案,这将是明确的,不易出错。请参阅以下代码;很多东西只是在编译时计算出来的 应该生成有效的代码。

template<int VALUE, int INDEX, int SIZE, bool ALLOW>
struct Assign
{
  static void Element (int *p) 
  {
    Assign<VALUE + 1, INDEX + 1, SIZE, (INDEX < SIZE)>::Element(p);
    p[INDEX] = VALUE;
  }
};
template<int VALUE, int INDEX, int SIZE>
struct Assign<VALUE, INDEX, SIZE, false>
{
  static void Element (int *p) { p[INDEX] = VALUE; }
};

template<int START, int SIZE>
void Initialize (int (&a)[SIZE])
{
  Assign<START, 0, SIZE, true>::Element(a);
}

乍一看可能有点复杂,但可以理解。它仍然可以更加通用。用法将如下所示:

int a[100];
Initialize<1>(a);  // '1' is the starting value

这可以用于任何 int a[N]。这里是 输出代码


2
2018-05-21 09:31



如果你想使用模板,那么就不需要自己编写了。您可以使用现有的: ideone.com/hQG8l ..然后+1为尝试。 :-) - Nawaz
@Nawaz,我觉得上面的模板解决方案可以在编译时完成大部分工作,并且会产生几乎相同的效果, int a[100] = { 1, 2, 3, ..., 99 }; (它将避免运行时 i++大型数组的操作类型)。 - iammilind
就数组而言,它在编译时没有任何作用。每个数组元素都有一个函数调用。 - Nawaz
@Nawaz,我的指示是针对我的模板解决方案;它将在编译时完成。但是,我不能保证,但我 感觉 编译器应该能够优化代码并制作一行代码 p[INDEX] = VALUE; 在实际代码中将等同于 int a[100] = { 1, 2, 3, ..., 99 };。但同样,我的知识缺乏编译器。 :) - iammilind
我也在谈论你的模板解决方案。正好有101个函数调用。 1来电 Initialize 功能等 100 打电话给 Assign 每个实例化模板中的函数。 - Nawaz


一些简单的代码生成怎么样。

#include <fstream> 

int main() {

    std::ofstream fout("sequence_macros.hpp");

    for(int i=1; i<=100; ++i)
    {
        fout << "#define e" << i << "(a) ";
        fout << "(a+0)";
        for(int j=1; j<i; ++j)
        {
            fout << ",(a+" << j << ")";
        }
        fout << '\n';
    }
}

然后把它用于:

#include <iostream>
#include "sequence_macros.hpp"

int main()
{
   // Create an array with 157 elements, in
   // sequence, starting at 25
   int x[] = {e100(25),e50(25+100),e7(25+100+50)};
   int sz = sizeof(x) / sizeof(*x);
   for(int i=0; i<sz; ++i)
      std::cout << x[i] << '\n';
}

生成代码: http://www.ideone.com/iQjrj

使用代码: http://ideone.com/SQikz


1
2018-05-21 07:35



这需要多次编译并运行代码;我认为OP需要更简单的解决方案或增强功能。 - iammilind
+1。我喜欢这个想法,这个想法实际上是基于我已经实现的想法。此外,它使用不同的程序生成宏。可以合并吗? - Nawaz
@Nawaz,合并这两个;我们要加强上面的程序,这将首先提出来 #define 然后运行实际的可执行文件。 - iammilind
@iammilind:ideone的第二个链接是合并,但这不是我的意思。我的意思是,由于@ Benjamin的解决方案有太多的宏,我们可以编写更少数量的宏来做越来越多不同类型的工作。它们不是很通用的。 - Nawaz