编译时从函数指针模板参数中获取目标函数名称
获取模板参数函数指针的函数名(C++20跨平台方案)
问题概述
你希望通过模板参数传入函数指针,在编译期或运行时获取该函数的名称或完整签名,当前使用C20,支持macOS、Linux、Windows,允许使用编译器扩展。这一需求确实属于**C反射特性**的范畴——目前C20及更早标准没有原生支持这类能力,需要依赖编译器扩展或平台API实现,未来的C反射标准会原生提供这类元信息获取能力。
编译期方案(consteval,依赖编译器扩展)
利用GCC/Clang的__PRETTY_FUNCTION__或MSVC的__FUNCSIG__,结合字符串解析提取函数名,完全在编译期完成计算:
#include <string_view> #include <algorithm> template <class T, T (*Fn)(T)> struct Foo { static consteval std::string_view printFnName() { #ifdef __GNUC__ || __clang__ constexpr std::string_view pretty_func = __PRETTY_FUNCTION__; // GCC/Clang格式示例:"static consteval std::string_view Foo<T, Fn>::printFnName() [with T = int; T (*Fn)(T) = bar]" constexpr auto start = pretty_func.find("= ") + 2; constexpr auto end = pretty_func.find(']', start); return pretty_func.substr(start, end - start); #elif _MSC_VER constexpr std::string_view func_sig = __FUNCSIG__; // MSVC格式示例:"class std::basic_string_view<char,struct std::char_traits<char> > __cdecl Foo<int,int (__cdecl*)(int)>::printFnName(void)" constexpr auto sig_end = func_sig.find("::printFnName") - 1; constexpr auto name_start = func_sig.rfind(',', sig_end) + 2; return func_sig.substr(name_start, sig_end - name_start); #endif } }; // 测试代码 int bar(int) { return 0; } #include <iostream> #include <cstdlib> // for std::abs int main() { std::cout << Foo<int, bar>::printFnName() << '\n'; std::cout << Foo<float, std::abs>::printFnName() << '\n'; return 0; }
说明:
- GCC/Clang下输出:
bar、std::abs - MSVC下输出:
bar、std::abs - 若需要完整函数签名,可调整字符串解析逻辑,提取
__PRETTY_FUNCTION__或__FUNCSIG__中的完整类型描述部分。
运行时方案(跨平台,依赖系统API)
如果允许运行时求值,可通过平台原生API获取函数指针对应的符号名,兼容性更稳定:
#include <string> #include <iostream> #ifdef __linux__ || __APPLE__ #include <dlfcn.h> #include <cxxabi.h> // 用于符号解混淆 #elif _WIN32 #include <windows.h> #include <dbghelp.h> #pragma comment(lib, "dbghelp.lib") #endif template <class T, T (*Fn)(T)> struct Foo { static std::string printFnName() { #ifdef __linux__ || __APPLE__ Dl_info info; if (dladdr(reinterpret_cast<void*>(Fn), &info) && info.dli_sname) { // 解混淆编译器修饰后的符号名 char* demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, nullptr); std::string name(demangled ? demangled : info.dli_sname); free(demangled); return name; } #elif _WIN32 char buffer[256]; DWORD64 addr = reinterpret_cast<DWORD64>(Fn); if (SymInitialize(GetCurrentProcess(), nullptr, TRUE)) { SYMBOL_INFO* sym_info = reinterpret_cast<SYMBOL_INFO*>(buffer); sym_info->SizeOfStruct = sizeof(SYMBOL_INFO); sym_info->MaxNameLen = sizeof(buffer) - sizeof(SYMBOL_INFO); if (SymFromAddr(GetCurrentProcess(), addr, nullptr, sym_info)) { char demangled[256]; UnDecorateSymbolName(sym_info->Name, demangled, sizeof(demangled), UNDNAME_COMPLETE); SymCleanup(GetCurrentProcess()); return demangled; } SymCleanup(GetCurrentProcess()); } #endif return "unknown"; } }; // 测试代码 int bar(int) { return 0; } #include <cstdlib> int main() { std::cout << Foo<int, bar>::printFnName() << '\n'; std::cout << Foo<float, std::abs>::printFnName() << '\n'; return 0; }
说明:
- Linux/macOS需链接
-ldl编译选项,Windows自动链接dbghelp.lib - 代码内置了符号解混淆逻辑,可将编译器生成的修饰名(如MSVC的
?bar@@YAHH@Z)转换为可读的函数名
关于C++反射的未来
当前C20及C23的反射特性(如std::reflect相关技术规范)仍处于完善阶段,完整的反射能力预计会在后续标准中正式纳入,届时可以原生地获取函数指针对应的元信息(名称、签名、参数类型等),无需依赖编译器扩展或平台API。
内容的提问来源于stack exchange,提问作者PluginPenguin
相关产品推荐
相关产品推荐

