std::source_location是C++20引入的编译期源位置设施,用于可靠获取调用点的文件、行号、列号和函数名;它通过默认参数机制自动注入调用者信息,优于__FUNCTION__等宏,但受编译器优化和内联影响可能返回空或"???"。

std::source_location 是什么,为什么不能直接用 __FUNCTION__
std::source_location 是 C++20 引入的标准设施,用于在编译期捕获调用点的文件、行号、列号和函数名。它比宏 __FUNCTION__ 或 __func__ 更可靠:后者只是字符串字面量,不参与类型系统,且在内联/模板展开后可能指向被调用函数而非实际调用者;而 std::source_location::function_name() 在绝大多数编译器(GCC 12+、Clang 13+、MSVC 19.30+)中能正确回溯到**直接调用点**的函数名——前提是别被优化掉或误传。
如何让日志宏自动注入调用者函数名
关键不是手动传参,而是利用 std::source_location::current() 的隐式默认参数特性。它必须作为函数最后一个参数(且有默认值),否则编译器不会自动填充:
void log_info(const char* msg, const std::source_location& loc = std::source_location::current()) {
printf("[%s:%d %s] %s\n",
loc.file_name(), loc.line(),
loc.function_name(), // 这里拿到的是调用 log_info 的函数名
msg);
}- 调用
log_info("hello")时,loc自动填入上层函数的源位置 - 不能写成
log_info("hello", std::source_location::current())—— 这会把log_info自己当作调用者 - 不能把
std::source_location放在参数列表中间或开头,否则默认参数失效
在模板日志函数里怎么用才不出错
模板函数同样支持默认 std::source_location 参数,但要注意实例化行为:
template<typename... Args>
void log_fmt(const char* fmt, Args&&... args,
const std::source_location& loc = std::source_location::current()) {
fprintf(stderr, "[%s:%d %s] ", loc.file_name(), loc.line(), loc.function_name());
vfprintf(stderr, fmt, std::make_format_args(args...));
fputc('\n', stderr);
}- 每个模板实例(如
log_fmt<int>("x={}", 42)</int>)都会生成一个带独立loc默认值的函数,没问题 - 如果模板参数包
Args...在std::source_location之前,会导致编译失败:C++ 不允许非尾部默认参数 - MSVC 对
std::make_format_args支持较晚(19.33+),若用旧版,改用printf+std::vsnprintf更稳妥
为什么有时 function_name() 返回空字符串或 "??"
这是常见陷阱,根源在编译器实现和优化策略:
立即学习“C++免费学习笔记(深入)”;
- GCC 在
-O2及以上可能将function_name()优化为空串,加-g或-frecord-gcc-switches可缓解,但不保证 - Clang 默认行为更稳定,但启用
-flto后跨 TU 调用可能丢失函数名 - 所有编译器对内联函数(
inline、constexpr、模板实例)的function_name()都可能返回调用者而非定义者——这其实是符合标准的,因为std::source_location::current()捕获的是“调用该表达式的位置”,不是“该函数定义的位置” - 如果你发现总是返回
"???",检查是否忘了链接-lstdc++(GCC)或启用了/Zi(MSVC)
真正可靠的函数名来源仍是 __func__ 宏——但它只能在函数体内用,没法塞进通用日志函数。所以实际工程中,常把两者结合:优先用 std::source_location::function_name(),fallback 到 __func__(通过宏包裹),并接受它在某些构建配置下不可用的事实。


















