C++如何在编译期提取__FILE__文件名并拼接__LINE__行号
编译期拼接文件名与行号实现方案
问题背景
尝试在编译期将带路径的源文件信息转换为文件名:行号的格式(例如将/tmp/main.cpp对应位置转换为main.cpp:20),初始实现代码如下:
#include <array> #include <iostream> using namespace std; #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x #define LINE_STRING STRINGIZE(__LINE__) constexpr const char* file_name(const char* path) { const char* file = path; while (*path) { if (*path++ == '/') { file = path; } } return file; } constexpr std::size_t str_size(const char* str) { auto i = str; while (*i != '\0') ++i; const auto length = i - str; return length; } template <std::size_t N1, std::size_t N2> constexpr std::array<char, N1 + N2 + 2> formatFilename(const char* name, const char* line) { auto total_size = N1 + N2 + 2; std::array<char, N1 + N2 + 2> res{}; std::size_t i = 0; for (; i < N1; ++i) { res[i] = name[i]; } res[i++] = ':'; for (int j = 0; j < N2; ++j) { res[i + j] = line[j]; } res[total_size - 1] = '\0'; return res; } int main() { constexpr char *p = &( formatFilename<str_size(file_name(__FILE__)), str_size(LINE_STRING)>( file_name(__FILE__), LINE_STRING)[0]); cout << p << endl; }
编译时抛出如下错误:
main.cpp: In function ‘int main()’:
main.cpp:46:46: error: call to non-‘constexpr’ function ‘std::array<_Tp, _Nm>::value_type& std::array<_Tp, _Nm>::operator[](std::array<_Tp, _Nm>::size_type) [with _Tp = char; long unsigned int _Nm = 12; std::array<_Tp, _Nm>::reference = char&; std::array<_Tp, _Nm>::value_type = char; std::array<_Tp, _Nm>::size_type = long unsigned int]’
错误原因
C++17标准中std::array仅const版本的下标运算符是constexpr的,非const重载不支持常量求值。原代码直接对formatFilename返回的临时数组对象调用[0],会匹配非const版本的下标运算符,因此触发编译错误。此外临时对象的地址不具备常量属性,无法直接用于初始化constexpr指针。
修正实现(C++17兼容)
修改点:
- 用
static constexpr变量存储拼接结果,保证对象具备静态存储期、地址为常量 - 调用const版本的数组访问接口,匹配constexpr重载
- 修正循环索引的类型不匹配问题
#include <array> #include <iostream> using namespace std; #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x #define LINE_STRING STRINGIZE(__LINE__) constexpr const char* file_name(const char* path) { const char* file = path; while (*path) { if (*path++ == '/') { file = path; } } return file; } constexpr std::size_t str_size(const char* str) { auto i = str; while (*i != '\0') ++i; return i - str; } template <std::size_t N1, std::size_t N2> constexpr std::array<char, N1 + N2 + 2> formatFilename(const char* name, const char* line) { constexpr std::size_t total_size = N1 + N2 + 2; std::array<char, total_size> res{}; std::size_t i = 0; for (; i < N1; ++i) { res[i] = name[i]; } res[i++] = ':'; for (std::size_t j = 0; j < N2; ++j) { res[i + j] = line[j]; } res[total_size - 1] = '\0'; return res; } int main() { static constexpr auto file_loc = formatFilename<str_size(file_name(__FILE__)), str_size(LINE_STRING)>( file_name(__FILE__), LINE_STRING); constexpr const char* p = file_loc.data(); cout << p << endl; return 0; }
更简便的实现方式
- 如果使用C++20及以上标准,
std::array的非const下标运算符、data()方法均已支持constexpr,甚至可以直接用constexpr std::string完成字符串拼接,无需手动操作字符数组。 - GCC、Clang编译器提供内置宏
__FILE_NAME__,直接展开为不带路径的当前源文件名,不需要手动实现路径裁剪逻辑,可以直接和行号拼接使用。
内容的提问来源于stack exchange,提问作者user8101155
相关产品推荐
相关产品推荐

