【问题标题】:Why __func__, __FUNCTION__ and __PRETTY_FUNCTION__ aren't preprocessor macros?为什么 __func__、__FUNCTION__ 和 __PRETTY_FUNCTION__ 不是预处理器宏?
【发布时间】:2025-11-26 23:15:03
【问题描述】:

我刚刚注意到 __func____FUNCTION____PRETTY_FUNCTION__ 不被视为预处理器宏,并且在标准的 16.8 预定义宏名称部分中没有提及(N4527 Working Draft)。

这意味着它们不能用于phase 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函数生成的月份名称相同,第一个如果值小于 10,则 dd 的字符为空格字符。如果翻译日期不是 可用,应提供实现定义的有效日期。

__FILE__

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

__TIME__

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

__func__ 被标准提及为以下形式的函数局部预定义变量:

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

所以事实是它是一个局部变量,因此字符串连接技巧不适用于它。

至于__FUNCTION____PRETTY_FUNCTION__ 并没有在标准中提及(是否定义了实现?),但认为它们的行为会像__func__ 是一个相当安全的选择。

所以问题是:为什么__func____FUNCTION____PRETTY_FUNCTION__ 是函数局部静态常量数组,而__FILE____DATE____TIME__ 是字符串文字?这个决定背后的理由是什么(如果有的话)?

【问题讨论】:

  • 它比你想象的还要糟糕,它们甚至不是文字......如果它们是你可以例如在构造函数中使用 constexpr 函数进行编译时哈希,以获得超级简单、超级便宜的 mini-RTTI 实现(对于序列化来说已经足够好了)。当您尝试时,编译器会告诉您“不是常量表达式”。
  • @Damon 这是一个非常有趣的观察,你有你描述的行为的例子吗?我也想自己测试一下。
  • 大约一年前我就尝试过这种方法,因为它似乎是制作 RTTI 系统的好方法,它需要为每个类存储一个静态整数。您需要存储 something,并且构造函数名称(即类名)的编译时散列似乎很理想。函数的名称显然也是一个编译时常量(它实际上并没有太多改变的方式,是吗!)。但 GCC 不喜欢这个想法,因为 __func__ 类似(小写或大写,漂亮与否)都不是常量表达式。

标签: c++ c-preprocessor standards


【解决方案1】:

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

一些实现结合了预处理和解析,在这些实现中,__func__ 可以按照您希望的方式工作。事实上,如果我没记错的话,MSVC 的__FUNCTION__ 就是这样工作的。但是,将翻译阶段分开是对实现的不合理要求。

【讨论】:

  • 很高兴看到反射研究组 (SG7) 是否有针对 C++1z 的解决方案
  • 我用头撞墙了两天,以为我不理解预处理器指令。我能想到的最好的替代方法是将签名替换为存储它的预处理器定义,并使用下一行的变量扩展为签名。在函数内部,可以根据需要对同一变量进行字符串化、连接等。我不喜欢预处理器解决方案限制编译器的代码编写方式,所以我愿意接受替代方案。如果您需要 C 字符串,也许 sprintf 进入错误消息?否则 STL?
  • @JohnP,但是您如何将这个变量从一个函数覆盖到下一个函数?这不会破坏一个以上的功能吗?