std::source_location::current() 必须作为带默认值的函数参数使用才能捕获调用点位置,若在函数体内调用则返回函数定义处位置;推荐用 inline 函数封装,避免宏副作用且保证调用点上下文准确。

std::source_location 不能在宏里用,但可以封装成函数调用
直接在普通函数里用 std::source_location::current() 拿到的是该函数内部的 location(即函数定义处),不是调用者位置。这是初学者最容易误解的一点——它不像宏那样天然具有调用点展开能力。
要真正捕获调用者上下文,必须让编译器在调用点“插入” location 信息。C++20 要求编译器对 std::source_location::current() 做特殊处理:当它出现在函数参数默认值中时,编译器会自动填入调用点的位置。
- 必须把
std::source_location设为函数参数,并赋予默认值std::source_location::current() - 不能把它放在函数体内调用,否则得到的是函数体所在行
- 参数类型必须是值传递(
std::source_location是 trivial 类型,按值传开销极小)
正确封装方式:带默认参数的内联函数
最常用且安全的做法是写一个 inline 函数,把 location 作为最后一个带默认值的参数。这样既避免宏的副作用,又能保证调用点捕获。
inline void log_debug(const char* msg, std::source_location loc = std::source_location::current()) {
printf("[%s:%d] %s\n", loc.file_name(), loc.line(), msg);
}调用时完全不用传 location:
立即学习“C++免费学习笔记(深入)”;
log_debug("value is ready"); // 自动捕获此处的文件、行号、函数名注意:std::source_location 的 function_name() 在不同编译器行为不一致(GCC 返回空或简单名,Clang/MSVC 更完整),别依赖它做逻辑判断。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
为什么不能用 auto 或模板推导来“偷懒”
有人尝试写模板函数想泛化处理,比如:
template<typename T> void report(T&& val, std::source_location loc = std::source_location::current());
这看似灵活,但容易踩坑:
- 模板实例化发生在编译期,每个调用点生成独立函数,
std::source_location::current()确实能捕获各自调用点 —— 这点没问题 - 但若模板参数涉及复杂类型(如
std::string、lambda),可能触发隐式拷贝或构造,影响性能和语义 - 更隐蔽的问题:某些编译器(尤其是旧版 GCC)对模板中
std::source_location::current()的支持不完整,可能始终返回模板定义位置
所以除非明确需要泛型,否则优先用非模板的 inline 函数。
和宏相比,函数方案的局限与取舍
函数方案无法像宏那样访问调用点的原始表达式(比如 LOG(x + y) 中的 x + y 字符串),也不能做 token pasting 或条件编译。如果你真需要打印表达式本身,还是得用宏包裹一层:
#define LOG_EXPR(expr) log_expr(#expr, (expr), std::source_location::current()) void log_expr(const char* expr_str, auto&& val, std::source_location loc);
但这里宏只负责字符串化和转发,真正的 location 捕获仍交给函数完成。纯函数方案做不到这点,这是设计边界 —— 不是缺陷,而是语言机制决定的。
真正容易被忽略的是:std::source_location 的字段(file_name()、line())返回的是 const char*,指向编译器嵌入的只读字符串,生命周期贯穿整个程序运行期,但不要试图修改或长期缓存指针本身(虽然通常安全,但标准未保证跨 translation unit 的地址稳定性)。

















