【问题标题】:Using nested macro with different number of arguments in C++在 C++ 中使用具有不同数量参数的嵌套宏
【发布时间】:2017-05-31 05:41:34
【问题描述】:

g++ -std=c++11 编译器编译以下代码失败。

    # include<iostream>
    # include<vector>

    using namespace std;


    # define stlf(x)        x.begin(), x.end()
    # define repf(it, a, b) for(auto it = a ; it != b ; ++it)


    /*
    // Also, following alternative fails

    # define repf(it, a, b) for(auto it = a ; it != b ; ++it)
    # define stlf(x)        x.begin(), x.end()

    */



    typedef vector<int > vi;

    # define pd(x)  printf("%d", x);

    int main(void){

        vi arr(10, -1);

        repf(arr, stlf(arr))
            pd(arr[i]);


        return 0;
    }

1.为什么会这样?

2. 对于 C++ 预处理器的实现者来说,他们避免了这个特性可能是什么实现问题?

3.我怎样才能使用这样的快捷方式??

【问题讨论】:

标签: c++ c++11 macros c-preprocessor


【解决方案1】:

您的两个选择是相同的。定义宏的顺序与它们的扩展无关;它只与它们在扩展时的定义相关。

为什么会这样?

您正在使用两个参数调用宏 repf,但它需要三个参数。这是一个错误,简单明了,因此预处理失败。

C++ 预处理器实现者可能会遇到哪些实现问题,他们避免使用此功能?

我认为您在这里做出了毫无根据的假设。问题不在于预处理器“缺少”某些“功能”;是你对预处理器工作方式的期望是错误的。

大概,你希望预处理器做这样的事情:

  1. repf(arr, stlf(arr))
  2. repf(arr, arr.begin(), arr.end())
  3. for(auto it = arr.begin() ; it != arr.end() ; ++it)

...从第 1 步到第 2 步,stlf(arr) 被扩展;然后它的扩展被放入对repf的调用中,然后在第3步被扩展。

问题是,预处理器不是这样工作的。鉴于示例已损坏,我无法正确说明这些步骤,因此我们假设我们这样做是为了说明:

#define FOO(X, Y) BAR(X, Y)
#define BAR(X,Y,Z) x is X y is Y z is Z
#define ACOMMAB A, B
FOO(ACOMMAB, C)

最后一行扩展为x is A y is B c is Z,它的工作方式更像这样:

  1. FOO(ACOMMAB, C)
  2. BAR(ACOMMAB, C)
  3. BAR(A, B, C)
  4. x is A y is B c is Z

请注意,内部宏不会先展开;相反,外部宏可以。还要注意,这个例子注入了一个逗号;所以注入逗号绝对是你实际上可以做的事情,我认为这是你提到的被避免的“功能”。

我怎样才能使用这样的快捷方式??

鉴于预处理器的工作方式并非您认为的那样,您可能不想用它来做您认为您想用它来做的事情……即使是为了加快编码速度。 stlf 很乐意为您构建两个用于函数调用的参数,但宏不是函数。远程似乎是你最好的选择。

【讨论】:

  • 很好的解释!!那么,如果执行顺序是从内到外,这不是可能的吗?以及任何验证向外向内扩张的方法?
  • @premktiw "任何方式来验证向外到向内的扩张" 不要按字面上描述的顺序进行;这个“命令”只是描述行为的一种手段。这里的关键是参数的扩展应用于替换列表,而不是调用,这可以通过我给出的示例有效但您的原始示例无效(在 g++ 中您可以使用-E 标志和-P 标志将清除此类输出)。此行为由标准指定。
  • ...“订单”设备还有助于可视化异常。例如,是否甚至扩展参数取决于替换列表;字符串化的参数或被粘贴的参数不会被扩展。实现也可以放弃扩展没有出现在替换列表中的参数。但是,严格来说,扩展参数可能会在将其扩展放入替换列表之前发生。
  • 为垃圾评论道歉,但仔细想想,这个顺序字面意思适用的相关意义:首先要做的主要事情是扩展外部宏。这种扩展发生在一系列步骤中。如果发生内部宏扩展,则在这些步骤之一期间发生。
【解决方案2】:

正如 H Walters 所指出的,问题在于宏执行操作的顺序

  • 从输入令牌流中读取(解析)参数
  • 在这些参数中扩展宏(除非它们与 # 或 ## 运算符有关)
  • 将那些(扩展的)参数替换到宏的主体中
  • 重新扫描正文以查找其他宏以展开。

请注意,上面有两个地方可能会扩展其他宏,因此虽然您想做的事情的顺序可能是错误的,但您可以通过添加额外的宏调用来使其正确在你想要的时候发生。例如,如果你有一个宏

#define expand(...)   __VA_ARGS__

什么都不做(只接受一个或多个参数并在其中扩展宏),您可以使用它在正确的位置获得额外的宏扩展:

expand( repf expand((arr, stlf(arr))) )

将扩展为您想要的for(auto it = arr.begin() ; it != arr.end() ; ++it)

这可能看起来很笨拙,但您可以将展开的调用包装在另一个递归宏中:

# define stlf(x)        x.begin(), x.end()
# define expand(...) __VA_ARGS__
# define repf(...) expand(repf2 expand((__VA_ARGS__)))
# define repf2(it, a, b) for(auto it = a ; it != b ; ++it)

现在repf(arr, stlf(arr)) 可以扩展您想要的方式。

【讨论】:

    【解决方案3】:

    repf 需要 3 个参数,而您只传递了 2 个。您不能使用另一个宏的扩展结果 (stlf) 作为参数(用于另一个宏)。预处理器/编译器不是这样设计的。

    我并不反对宏(它们在很多情况下都非常有用),但是对于这种简单的情况,您一定不能使用宏。它使您的程序难以阅读、维护和调试。避免!

    理想情况下,您应该使用基于范围的 for 循环(无宏):

    for(int a : arr) ...
    

    【讨论】:

    • 我可以使用一个宏的扩展作为参数!我的主要问题是问题 2。同样在竞争性编程中,这些快捷方式可以节省大量时间 :) 所以我真的需要它来工作。例如作为参数的宏扩展:sort(stlf(arr));
    【解决方案4】:

    应该是这样的:

    define repf(it, a) for(auto it = std::begin(a) ; it != std::end(a) ; ++it)
    

    但是您为什么要为此使用宏?

    【讨论】:

    • 不是这样的,以便将 repf(it, a, b) 也用于一般循环,例如: int i; repf(i, 0, 10) :- 将为我迭代 10 次!! :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    相关资源
    最近更新 更多