std::source_location 不能无宏获取文件名和行号,因其 current() 函数本质依赖编译器在调用点注入宏信息,无法通过普通函数、模板或 consteval 绕过,唯一标准可移植方式是直接使用 current() 或封装为宏。

source_location 不能无宏获取文件名和行号
这是 C++20 的一个常见误解:std::source_location 的构造函数是 隐式调用且仅支持编译器内建插值,必须靠预处理器宏(如 __FILE__、__LINE__)触发。你写 std::source_location::current() 看似没显式宏,但它的实现本质就是编译器在调用点自动展开成宏参数——标准明确要求它“由实现以调用点的上下文初始化”。
为什么 std::source_location::current() 本质仍是宏驱动
查看典型实现(如 libstdc++ 或 libc++)会发现:std::source_location::current() 是一个 constexpr 函数,但其返回对象的字段(file_name()、line() 等)由编译器在 调用处 直接注入字面量。这和手写 source_location{__FILE__, __LINE__, ...} 没有语义区别,只是语法糖。
- 不调用
current(),就拿不到当前上下文;而调用它就必须出现在具体代码行上——这正是宏行为的特征 - 无法把它封装进普通函数里再调用:比如
auto get_loc() { return source_location::current(); },此时拿到的是get_loc函数体内的位置,不是调用者的 - 试图用模板或
consteval绕过?不行。标准禁止用户手动构造含有效源信息的source_location实例(除非用current())
替代方案只有两种真实路径
如果你的目标是“避免手写宏但又想获得调用点信息”,实际只有两个可行方向:
-
继续用
source_location::current():它是最轻量、零开销、标准保证的方式,虽依赖隐式宏机制,但接口干净,无需自己拼接字符串或处理宽窄字符 -
用传统宏封装 + source_location 成员变量:例如定义
#define HERE std::source_location::current(),然后在日志函数中接收std::source_location loc = HERE。这不是“无宏”,而是把宏收口到一处,避免重复写current() - 放弃编译期信息,改用运行时栈回溯(如
backtrace()+addr2line):性能差、不可靠、不跨平台,且拿不到精确列号和原始文件路径
容易被忽略的兼容性细节
即使接受 current(),也要注意几个硬伤:
立即学习“C++免费学习笔记(深入)”;
- MSVC 19.30+ 才完全支持
file_name()返回 UTF-8(旧版返回窄字符但编码未定义) - Clang 12+ 和 GCC 11+ 行为一致,但若编译时用了
-frecord-gcc-switches或调试信息被 strip,file_name()可能返回空或相对路径 -
source_location对象本身是 trivially copyable,但file_name()返回的是const char*,指向只读内存——别存指针,要拷贝字符串 - 它不提供函数名、列号(column)、模板实例化上下文等信息,这些仍需其他工具链配合
真想绕过宏,目前没有符合 C++ 标准的可移植办法。所谓“无宏”诉求,本质上是在拒绝所有编译期注入机制——而源码位置信息天生就属于编译期元数据,运行时根本不存在对应实体。


















