【问题标题】:How to write a c++ assert macro with a varying number of informational arguments?如何编写具有不同数量信息参数的 C++ 断言宏?
【发布时间】:2014-09-17 15:25:48
【问题描述】:

我正在尝试编写一个类似于标准assert 的宏dbgassert。除了assert 所做的,我还想dbgassert 打印任意数量的附加参数(包含调试信息)。

我目前掌握的内容如下,改编自this SO answer。但是我在代码中遇到了可变参数模板或宏的问题。如果我使用至少一个附加参数(OK 行),那么 dbgassert 将按预期工作。但是如果我没有给出额外的论点,那么编译就会失败(问题行)。

我有一些可变参数模板编程的经验(例如如何打印元组),但我之前没有使用过可变参数宏。

有人能解释一下编写这个可变参数宏组合的正确方法是什么吗?

顺便说一句,有人能解释一下宏中的#EX 魔法吗?它显示了表达式并在 gcc4.8.1 上为我工作。是否普遍支持?

谢谢,


代码:

//corrected reserved identifier issue and assumption issues per comments
#include <cassert>
#include <iostream>
using namespace std;

template <typename ...Args>
void realdbgassert(const char *msg, const char *file, int line, Args ... args) {
  cout << "Assertion failed! \nFile " << file << ", Line " << line << endl 
       << "  Expression: " << msg << endl;
  std::abort();
}

#define dbgassert(EX,...) \
  (void)((EX) || (realdbgassert (#EX, __FILE__, __LINE__, __VA_ARGS__),0))

int main() {
  dbgassert(1>2,"right","yes"); //OK
  dbgassert(1>2,"right"); //OK.
  //dbgassert(1>2); //Problem. compile error: expected primary-expression before ')' token
                  //#define dbgassert(EX,...) (void)((EX) || (realdbgassert (#EX, __FILE__, __LINE__, __VA_ARGS__)^,0))
}

代码的原始版本。

#include <cassert>
#include <sstream>
using namespace std;

#ifdef __cplusplus
extern "C" {
#endif
extern void __assert (const char *msg, const char *file, int line);
#ifdef __cplusplus
};
#endif

template <typename ...Args>
void _realdbgassert(const char *msg, const char *file, int line, Args ... args) {
    stringstream os;
    //... do something
    __assert(msg,file,line);
}
#define dbgassert(EX,...) (void)((EX) || (_realdbgassert (#EX, __FILE__, __LINE__, __VA_ARGS__),0))

int main() {
  dbgassert(1==0,"right"); //Problem line: undefined reference to `__assert'
} 

【问题讨论】:

  • 您正在使用reserved identifiers。无论如何,__assert 是什么?当然不是标准功能。还是您只是想使用您的实现没有的实现细节?
  • .../ccZ47ZOL.o:test7.cpp:.text$_Z14_realdbgassertIIPKcEEvS1_S1_iDpT_[_Z14_realdbgassertIIPKcEEvS1_S1_iDpT_]+0x65): undefined reference to __assert'
  • @TingL 好吧,你期待什么?您尚未提供__assert 的定义。还是说你的标准库实现提供了这样的功能?
  • 我认为__assert 是assert 函数背后的函数。我在这里看到它:stackoverflow.com/questions/9701229/…
  • 我觉得标准的assert不可能是真正的函数,assert宏后面一定有真正的函数吧?因为assert 报告问题行,如果assert 是一个真正的函数,它会报告&lt;cassert&gt; 中的行号。标准中是否没有标准处理程序(即真正的断言函数)?

标签: c++ c++11 assert variadic-templates variadic-macros


【解决方案1】:

您的问题是__VA_ARGS__ 的值在问题案例中为空。因此,当预处理器扩展realdbgassert(#EX, __FILE__, __LINE__, __VA_ARGS__) 时,结果是一个未完成的参数列表realdbgassert("1&gt;2", "foo.c", 42, )。请注意,由于__VA_ARGS__ 的空扩展,参数列表未正确终止。

要解决此问题,您需要使用某种技巧。最好的解决方案是调整情况,使__VA_ARGS__ 包含最后一个无条件参数,并在函数调用结束时将其与可选参数一起传递。这是最好的,因为它是标准 C。

我知道的另一个修复是对语言的 gcc 扩展:有关更多详细信息,请参阅 this gcc 文档页面,但您可以通过在 __VA_ARGS__ 前面添加双 ## 来修复宏:

#define dbgassert(EX,...) \
  (void)((EX) || (realdbgassert (#EX, __FILE__, __LINE__, ## __VA_ARGS__),0))

附:
# 是预处理器的字符串化运算符:它将宏参数的值转换为字符串文字,即。 e.而不是粘贴1&gt;2,而是粘贴"1&gt;2"。

【讨论】:

  • 我正努力在手边写一个答案,却发现你打败了我。很好的解释。你得到了我的支持。
【解决方案2】:

将标记粘贴运算符 (##) 放在 __VA_ARGS__ 之前。如果__VA_ARGS__ 为空,这将删除__VA_ARGS__ 之前的逗号。

您的宏将是:

#define dbgassert(EX,...) \
  (void)((EX) || (realdbgassert (#EX, __FILE__, __LINE__, ##__VA_ARGS__),0))

请注意,作为正确提及的其他海报之一,令牌粘贴是 GNU CPP 扩展(请参阅https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html)。

MS 编译器(在 VS2010 下测试)不需要标记粘贴,如果__VA_ARGS__ 为空,它会简单地删除尾随逗号,请参阅:http://msdn.microsoft.com/en-us/library/ms177415(v=vs.110).aspx

【讨论】:

    【解决方案3】:

    作为说明,除了@cmaster 和@ds27680 的解决方案之外,我还能够找到另一种解决尾随逗号问题的方法。由于__VA_ARGS__ 会导致额外的逗号,我可以将__VA_ARGS__ 打包到std::tuple 或函数调用中,并将元组/结果用作real 函数 的参数。现在空的__VA_ARGS__ 不会有问题,因为它被打包到一个有效值中(即一个空元组或一个空函数的返回值)。我想这在代码中更长,但在不涉及## 的情况下更便携。

    以上两种情况分别显示在下面代码中的dbgassert和dbgassert1宏中。

    #include <cassert>
    #include <iostream>
    #include <tuple>
    using namespace std;
    
    template <typename ...Args>
    string print_tuple(tuple<Args...> tp) {
      return ""; //print the tuple...
    }
    
    template <typename ...Args>
    void realdbgassert(tuple<Args...> info,const char *msg, const char *file, int line) {
      cout << "Assertion failed! \nFile " << file << ", Line " << line << endl 
           << "  Expression: " << msg << endl
           << "  Info: " << print_tuple(info) << endl;
      std::abort();
    }
    
    #define dbgassert(EX,...) \
      (void)((EX) || (realdbgassert (std::tie(__VA_ARGS__),#EX,__FILE__, __LINE__),0))
    
    void realdbgassert1(string info,const char *msg, const char *file, int line) {
      cout << "Assertion failed! \nFile " << file << ", Line " << line << endl 
           << "  Expression: " << msg << endl
           << "  Info: " << info << endl;
      std::abort();
    }
    
    template <typename ...Args>
    string print_info(Args ... args) {
      return "";  //print stuff
    }
    
    #define dbgassert1(EX,...) \
      (void)((EX) || (realdbgassert1 (print_info(__VA_ARGS__),#EX,__FILE__, __LINE__),0))
    
    
    int main() {
      dbgassert(1>2,"right","yes"); //OK
      dbgassert(1>2,"right"); //OK
      dbgassert(1>2); //OK now
      dbgassert1(1>2); //OK too
    }
    

    【讨论】:

    • 我已经在考虑将无条件参数包装到一个对象中,以便可以将它们隐式传递给像 WrapperConstructor(#EX, __FILE__, __LINE__).assert(__VA_ARGS__) 这样的可变参数成员函数。但是您的方法可能更像是 C++ 风格。 +1 第一个答案是实际提供完整的便携式解决方案:-)
    【解决方案4】:

    您假设assert 是通过调用__assert 实现的。这很可能是一种特定实现的工作方式,但通常不能完全依赖。

    请改为关注the documentation:测试您的状况,失败时将诊断信息发送到标准错误,然后调用std::abort。

    【讨论】:

    【解决方案5】:

    您必须编写函数__assert 的内容 - 如果您指定它是extern,您应该将包含函数定义的文件附加到编译过程中。如果你不知道如何编写多文件程序,我真的帮不了你。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-20
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多