std::source_location 是 C++20 引入的轻量结构体,用于在编译期捕获调用点的源码位置信息。它不依赖运行时堆栈遍历,也不触发函数内联抑制(只要编译器支持 C++20 且未禁用),因此开销极低。关键在于:它不是“获取当前行号”,而是“让调用者把它的位置传进来”——这决定了你必须在日志宏里用 __LINE__ 或更规范的 std::source_location::current() 显式捕获。
直接把 std::source_location 当作函数参数传入即可,但注意默认值必须是编译期可求值的表达式。最常用写法是用静态成员函数 std::source_location::current() 作为默认实参:
void log_debug(const char* msg, const std::source_location loc = std::source_location::current()) {
std::cout << "[" << loc.file_name() << ":" << loc.line() << "] " << msg << "\n";
}调用时无需手动传参:log_debug("value is 42"); 就会自动记录调用点的文件名与行号。若手动传入(比如封装中间层),需确保调用点仍是最终用户代码位置,否则行号会偏移。
std::source_location::current() —— 那会记录包装函数自身的行号loc.function_name() 在 GCC/Clang 中通常返回空字符串;MSVC 支持较好,但不可依赖纯函数调用无法解决“用户调用点 vs 包装函数”的行号偏移问题,必须用宏。宏展开发生在预处理阶段,std::source_location::current() 在宏体内求值,就落在用户代码行上:
#define LOG_DEBUG(msg) \
log_debug(msg, std::source_location::current())这样 LOG_DEBUG("x = " + std::to_string(x)); 记录的就是这行本身的行号。更进一步,可支持格式化:
#define LOG_INFO(fmt, ...) \
do { \
std::ostringstream _oss; \
_oss << fmt; \
log_info(_oss.str().c_str(), std::source_location::current()); \
} while(0)注意:__VA_ARGS__ 展开后不能直接传给 std::source_location::current(),因为逗号会破坏宏参数解析;必须先拼接好字符串再传。
do { ... } while(0) 包裹可防止 if-else 分支中出现语法错误-std=c++20 后才支持 std::source_location;MSVC 需 VS 2019 16.8+典型现象包括:所有日志都显示同一行号(比如总显示在 log.h 第 42 行),或编译报错 error: 'std::source_location::current' is not a constant expression。
-std=c++20 或项目设置未启用 C++20 —— 这是最常见的原因std::source_location::current() —— 它不是 constexpr 函数,不能用于常量表达式std::source_location::current() 写在函数体内部而非默认参数位置,又没用宏 —— 行号永远是该函数定义处的行验证是否生效,可在不同文件、不同行各调用一次 LOG_DEBUG("test"),观察输出的 loc.line( 是否随调用位置变化。不变化,说明捕获点没落在用户代码上。
)