为什么__func__、__FUNCTION__和__PRETTY_FUNCTION__不是预处理器宏?

32
我刚刚注意到__func____FUNCTION____PRETTY_FUNCTION__没有被视为预处理器宏,并且它们没有在标准的16.8 预定义宏名称章节中提到(N4527 工作草案)。
这意味着它们不能在第6阶段的字符串连接技巧中使用:
// Valid
constexpr char timestamp[]{__FILE__ " has been compiled: " __DATE__ " " __TIME__};
// Not valid!!!
template <typename T>
void die() { throw std::runtime_error{"Error detected in " __PRETTY_FUNCTION__}; }
据我所知,标准规定__FILE____DATE____TIME__会被翻译成字符串字面值:

16.8 预定义的宏名称 [cpp.predefined]

__DATE__

源文件翻译的日期:"Mmm dd yyyy"形式的字符字面值,其中月份的名称与asctime函数生成的相同,并且如果dd的值小于10,则其第一个字符是空格字符。如果无法获取翻译日期,则应提供实现定义的有效日期。

__FILE__

当前源文件的假定名称(字符字面值)。

__TIME__

源文件翻译时的时间:"hh:mm:ss"形式的字符字面值,与asctime函数生成的时间相同。

标准中还提到了__func__,它是一种函数本地的预定义变量,形式如下:

static const char __func__[] = "function-name ";

事实是这是一个局部变量,因此使用字符串连接技巧无法在其上工作。

至于__FUNCTION____PRETTY_FUNCTION__没有在标准中提到(是否是实现定义?)但可以放心地认为它们的行为类似于__func__

那么问题是:为什么__func____FUNCTION____PRETTY_FUNCTION__是函数本地静态常量字符数组,而__FILE____DATE____TIME__是字符串字面值?这个决定背后有什么理由(如果有的话)?


5
比你想象的还要糟糕,它们甚至不是文字常量……如果是的话,你可以在构造函数中使用constexpr函数进行编译时哈希,以获得一个超级简单、成本极低的小型RTTI实现(仅足够用于序列化)。当你尝试这样做时,编译器会告诉你“不是常量表达式”。 - Damon
@Damon,这是一个非常有趣的观察结果,你有描述你所说的行为的示例吗?我也想亲自测试一下。 - PaperBirdMaster
我大约一年前尝试过这件事,因为它似乎是一种不错的方式来创建一个RTTI系统,该系统具有存储每个类的静态整数的开销。你需要存储“某些东西”,构造函数名称(即类名)的编译时哈希值似乎是理想的选择。函数名称显然也是编译时常量(它实际上没有太多改变的方式,对吧!)。但是GCC不喜欢这个想法,因为__func__之类的任何东西(小写或大写,漂亮或不漂亮)都不是常量表达式。 - Damon
1个回答

36

在预处理时扩展 __func__ 需要预处理器知道它正在处理哪个函数。预处理器通常不知道,因为解析发生在预处理器完成之后。

一些实现将预处理和解析组合在一起,在这些实现中, __func__ 可以按照您希望的方式工作。实际上,如果我记得正确,MSVC的 __FUNCTION__ 就是这样工作的。但对于将翻译阶段分开的实现来说,这是一个不合理的要求。


1
很希望看到反射研究小组(SG7)是否有C++1z的解决方案。 - KABoissonneault
1
我曾经因为理解预处理器指令的问题而苦恼两天,最好的替代方案是用预处理器定义代替签名并存储它,并在下一行使用变量扩展成签名。函数内部,可以将同一变量字符串化、连接等。我不喜欢那些对编写供编译器使用代码方式有限制的预处理解决方案,所以我愿意尝试其他替代方案。如果需要C字符串,也许可以使用sprintf格式化输出到错误消息中?否则用STL呢? - John P
@JohnP,但是你如何在一个函数到另一个函数中覆盖这个变量?那不会对多个函数产生影响吗? - Clément

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接