【问题标题】:How can I use C-style macros with C++11-style constructor calls?如何在 C++11 风格的构造函数调用中使用 C 风格的宏?
【发布时间】:2015-03-30 05:33:03
【问题描述】:

我发现使用 C 风格的宏和使用 C++11 中引入的新的统一列表初始化形式之间似乎不兼容,但这种事情绝对不可能编写出来,这似乎令人难以置信,所以我认为我遗漏了一些东西。

这是问题所在:当预处理器查找宏参数时,大括号似乎被忽略了。像 MACR(Range{2,4}) 这样的调用被误解为有两个参数,Range{2 和 4。在下面的代码中,一切都很好(嗯,风格很差,但它有效),直到标记的行:

#include <iostream>
using namespace std;

struct Range { int st, fn; };
ostream& operator << (ostream& out, const Range& r)
{ return out << "(" << r.st << "," << r.fn << ")"; }

#define COUT(X) (cout << (X) << endl)

int main()
{
  COUT(3);
  Range r {3,5};
  COUT(r);

  COUT(Range{3,5});  //this line won't compile
}

它给出以下错误消息:

badmacro.cpp:16:18: error: macro "COUT" passed 2 arguments, but takes just 1
   COUT(Range{3,5});
                  ^
compilation terminated due to -Wfatal-errors.

尤其是在使用较旧的库时,有时不可避免地会使用宏调用;在这些情况下,我们肯定不应该放弃新语法吗?有官方的解决方法吗?

【问题讨论】:

    标签: c++11 c-preprocessor


    【解决方案1】:

    如果您需要将表达式传递给现有宏,并且表达式包含非屏蔽逗号,只需将整个表达式括在括号中即可。

    COUT((Range{3,5}));
    

    丑吗?是的,但是当您使用宏时会发生这种情况。不要那样做。

    如果它不是一个表达式并且不能带额外的括号,那么你就不能使用那个宏。

    如果你正在编写一个你不应该编写的宏,有时你可以编写一个可变参数宏(如果你的编译器支持的话):

    #define COUT(...) cout << (__VA_ARGS__) << endl;
    

    【讨论】:

    • @RemyLabeau 一个 C++11 编译器应该支持它。
    • 可变参数宏是在 C++11 之前的 C99 中引入的。并不是每个人都在使用 C++11 编译器,也不是所有 C++11 之前的编译器都支持可变参数宏(有些支持)。
    • “如果它不是表达式并且不能带额外的括号”,那么它仍然可以工作。对于这个特定实例,#define ID(...) __VA_ARGS__,然后是COUT(ID(Range{3,5}))。
    • @RemyLebeau 这种初始化风格在 C++11 中是新的,并且已经需要 C++11 编译器。如果已经需要 C++11 编译器,不妨使用它。
    【解决方案2】:

    预处理器宏只是在编译之前进行花哨的文本替换。调用宏时,预处理器对参数列表的解析很少。有一些逻辑可以区分嵌套括号内的逗号和括号外的逗号,因此它知道哪些逗号属于宏本身的参数列表,哪些逗号属于嵌套函数调用的参数列表。例如:

    macro(param1, param2, func(param1, param2) )
    

    参数被解释为

    param1
    param2
    func(param1, param2)
    

    而不是

    param1
    param2
    func(param1
    param2)
    

    在您的情况下,您的逗号不在嵌套括号内,因此预处理器最终将参数列表 Range{3,5} 拆分为两个参数值

    Range{3
    5}
    

    因此出现错误是因为您的宏只接受一个参数。预处理器没有任何上下文信息知道Range{3,5} 应该被视为一个参数值。它只看到逗号并对其进行拆分。

    所以,要解决您的问题,请尝试添加一对额外的括号:

    COUT((Range{3,5}));
    

    然后预处理器应该解释一个参数值:

    (Range{3,5})
    

    这将创建以下语句供编译器使用:

    (cout << ((Range{3,5})) << endl);
    

    【讨论】:

      猜你喜欢
      • 2017-06-12
      • 2014-10-20
      • 1970-01-01
      • 2010-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-15
      • 2012-01-12
      相关资源
      最近更新 更多