我有一个
constexpr
函数,我试图从 __FILE__
宏中删除文件名,即删除除路径之外的所有内容。我草拟了这个基本函数来执行此操作,并且我将其制作为 constexpr
,希望编译器能够推断出结果并将计算结果作为字符串放入最终的二进制文件中。功能并不完美,只是一个简单的模型。
constexpr const char* const get_filename()
{
auto file{ __FILE__ };
auto count{ sizeof(__FILE__) - 2 };
while (file[count - 1] != '\\')
--count;
return &file[count];
}
int main()
{
std::cout << get_filename() << std::endl;
return 0;
}
问题在于,这在编译时没有被评估(构建:MSVC x64 Release Maximum Speed optimization)。我假设这是因为返回了一个指向二进制文件中常量字符串内的内容的指针,这本质上就是该函数正在做的事情。然而,我希望编译器做的是解析
get_filename
函数并以某种方式返回字符串文字 "main.cpp"
,例如,而不是返回该子字符串的指针。本质上,我希望对其进行编译,以便最终的二进制文件中只有 main.cpp
,而没有 __FILE__
宏的其他部分。这可能吗?
因为您不需要最终二进制文件中的完整
__FILE__
路径,所以我们必须将字符串复制到 std::array
:
constexpr auto get_filename()
{
constexpr std::string_view filePath = __FILE__;
constexpr auto count = filePath.rfind("\\");
static_assert(count != std::string::npos);
std::array<char, count> fileName{};
std::copy(filePath.data() + count + 1, filePath.data() + filePath.size(), fileName.data());
return fileName;
}
并在调用
constexpr
函数时指定get_filename
:
constexpr auto fileName = get_filename();
std::cout << fileName.data();
或者,从 C++20 开始,您可以使用
consteval
强制在编译时对其求值:
consteval auto get_filename();
这是对 godbolt 的测试,它使用
printf
而不是 std::cout
来实现更短的汇编。
像这样,我的代码位于名为 .\some_path\main.cpp (Windows 路径样式)的文件中:
#include <string_view>
#include <iostream>
static constexpr std::string_view get_filename()
{
std::string_view name{ __FILE__ };
auto pos = name.find_last_of('\\'); // or '/' on linux
if (pos != std::string::npos)
{
return std::string_view{ name.begin() + pos + 1, name.end() };
}
return name;
}
int main()
{
static_assert(get_filename() == "main.cpp");
std::cout << get_filename();
return 0;
}
当前接受的答案
thedemons
错误地计算了结果 std::array
的计数。它正在计算 __FILE__
中的字符数,直到 \\
的最后一个实例。当结果静态存储时,这会导致使用更多内存。这可能会导致编译器不使用堆栈,如 thedemon
的 godbolt 所示。
请参阅此替代解决方案: https://godbolt.org/z/s7P7bx5PG
#include <array>
#include <filesystem>
#include <iostream>
#include <string_view>
consteval auto get_source_file_name()
{
// Assumes the target filesystem is the same as the compiler host.
// Targeting Windows, this is "\\", but targeting Linux from Windows likely means this will include the full __FILE__
constexpr wchar_t target_filesystem_preferred_separator =
std::filesystem::path::preferred_separator;
constexpr std::string_view source_file_path = __FILE__;
constexpr size_t slash_offset = source_file_path.rfind(target_filesystem_preferred_separator) != std::string::npos
? source_file_path.rfind(target_filesystem_preferred_separator)
// No slash found, use the full string
: 0;
// This includes the nil character
constexpr size_t trimmed_file_path_length =
source_file_path.length() - slash_offset;
std::array<char, trimmed_file_path_length> trimmed_file_path{};
std::copy(source_file_path.data() + (slash_offset + 1), source_file_path.data() + source_file_path.length(), trimmed_file_path.data());
return trimmed_file_path;
}
static constexpr auto k_source_file_name_char_array = get_source_file_name();
int main()
{
// std::array is on the stack, at least when compiled with /02
constexpr auto file_name = get_source_file_name();
printf("%s\n", file_name.data());
// std::array is in .rdata
printf("%s\n", k_source_file_name_char_array.data());
return 0;
}