这个想法来自/sf/answers/1104286361/
我在 string_view 末尾添加了一个 '\0',这样我们就可以将 data() 用于 printf、spdlog 等。
我使用这个宏来打印带有类名的函数名。
但是,我发现编译器不够智能,无法内联字符串,而是需要先进行内存复制才能堆栈:
https://godbolt.org/z/bqob37G3z
请参阅 main 函数中 CALL 和 CALLFUNC 的区别。
是否可以告诉编译器将字符串放在某个 RO 部分中,例如 const char *?
template<std::size_t N>
consteval const std::array<char, N> __get_function_name(const char * arr)
{
std::array<char, N> data {};
std::string_view prettyFunction(arr);
size_t bracket = prettyFunction.rfind("(");
size_t space = prettyFunction.rfind(" ", bracket) + 1;
size_t i;
for (i = 0; i < bracket - space; i += 1) {
data[i] = arr[space + i];
}
data[i] = '\0';
return data;
}
#define __METHOD_NAME__ __get_function_name<strlen(__PRETTY_FUNCTION__)>(__PRETTY_FUNCTION__).data()
Run Code Online (Sandbox Code Playgroud)
感谢@nm 和 user17732522。我终于得到了一个可行的版本: https ://godbolt.org/z/sof1j3Md4
仍然不完美,因为该解决方案需要搜索“(”和“”两次,这可能会增加编译时间。
更新:仅调用 rfind(" ") 一次:https ://godbolt.org/z/zYcajqaje
#include <array>
#include <string_view>
constexpr size_t my_func_end(const char * arr)
{
std::string_view prettyFunction(arr);
return prettyFunction.rfind("(");
}
constexpr size_t my_func_start(const char * arr, const size_t end)
{
std::string_view prettyFunction(arr);
return prettyFunction.rfind(" ", end) + 1;
}
template<std::size_t S, std::size_t E>
constexpr const std::array<char, E - S + 1> my_get_function_name(const char * arr)
{
std::array<char, E - S + 1> data {};
size_t i;
for ( i = 0; i < E - S; i += 1) {
data[i] = arr[S + i];
}
data[i] = '\0';
return data;
}
template<auto tofix>
struct fixme
{
static constexpr decltype(tofix) fixed = tofix;
};
#define __METHOD_NAME_ARRAY__(x) my_get_function_name<my_func_start(x, my_func_end(x)), my_func_end(x)>(x)
#define __METHOD_NAME__ (fixme<__METHOD_NAME_ARRAY__(__PRETTY_FUNCTION__)>::fixed.data())
Run Code Online (Sandbox Code Playgroud)
不确定编译器是否可以缓存计算。如果是的话,那就足够了。
编译器没有意识到或考虑 的特定行为puts,即它不存储传递的指针,也不会再次调用其调用者。
问题是,如果没有这些知识,编译器必须考虑puts存储传递给全局变量的指针的可能性,然后再次调用其调用者,然后将新的指针参数与存储在变量中的旧指针参数进行比较。全局变量。它们必须比较不相等,因为它们在其生命周期中都是指向不同临时对象的指针。因此编译器无法重用相同的只读静态内存位置作为调用的参数puts。
因此,您需要明确告诉编译器使用静态内存位置:
#define METHOD_NAME get_function_name<my_strlen(__PRETTY_FUNCTION__)>(__PRETTY_FUNCTION__)
#define CALL() { static constexpr auto v = METHOD_NAME; puts(v.data()); }
Run Code Online (Sandbox Code Playgroud)
从技术上讲,constexpron与on 功能v是多余的。consteval只要constexpr两者都满足就足够了。
如果您不添加,constexpr您v可能需要添加const以确保编译器不需要考虑puts修改字符串内容的可能性,尽管示例中的 GCC 似乎意识到了这一点。(puts需要一个const char*作为参数不足以证明这一点。)
顺便说一句,你不能strlen在那里使用(假设你希望它能够移植到 GCC 以外的其他编译器,该编译器__PRETTY_FUNCTION__以你使用的方式支持,例如带有 libc++ 的 Clang)。GCC 在没有诊断的情况下允许它不符合标准,并且不能保证它可以在其他编译器上工作。std::strlen未按constexpr标准标记。您可以使用C++17std::char_traits<char>::length以来的替代方法constexpr。