嵌入式技术
打印类型名称,听起来像是一个很简单的需求,但在目前的C++当中,并非易事。
本文介绍了一些对此需求的分析与实现。具体实现如下:
template <typename...> struct type_name {}; template <typename... Ts> struct name_of { using X = typename type_name
error: no type named 'name' in 'struct type_name
template <typename T> void f(T t) { name_of<decltype(t)>(); } int main() { const int i = 1; f(i); }输出为:
error: no type named 'name' in 'struct type_name
namespace std { class type_info { public: virtual ~type_info(); bool operator==(const type_info& rhs) const noexcept; bool before(const type_info& rhs) const noexcept; size_t hash_code() const noexcept; const char* name() const noexcept; type_info(const type_info&) = delete; // cannot be copied type_info& operator=(const type_info&) = delete; // cannot be copied }; }其中的成员函数name()就可以返回类型的名称,这样就根据type获取到了value。但是标准说这个名称是基于实现的。
Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.事实上也的确如此,MSVC返回的是一段可读的类型名称,而gcc, clang返回的是Mangled Name。(Name Mangling内容可以参考【洞悉C++函数重载决议】)
但幸好,它们内部提供的有Demangle API,通过相关API就可以将类型名称转换为可读的名称。这个API定义如下:
namespace abi { extern "C" char* __cxa_demangle (const char* mangled_name, char* buf, size_t* n, int* status); }
这里主要关注第一个参数就可以,其他参数都可以置空。第一个参数就是type_info::name()返回的Mangled Name,返回值为Demangled Name。
因此,现在就可以分而论之,msvc直接使用type_info::name()返回的类型名称就可以;对于gcc/clang,则先使用Demangle API进行解析,次再使用。具体实现如下:#include
static const char __func__[] = "function-name";C++引入的这个说是"implementation-defined string",意思也是基于实现的,不过在三个平台上的输出基本是一致的。这个标识符只包含函数名称,并不会附带模板参数信息。但是与其相关的扩展附带有这部分信息,gcc/clang的扩展为__PRETTY_FUNCTION__,msvc的扩展为__FUNCSIG__。 它们的内容形式也是基于实现的,一个简单的例子如下。
template <typename T> consteval auto type_name() { #ifdef _MSC_VER return __FUNCSIG__; #elif defined(__GNUC__) return __PRETTY_FUNCTION__; #elif defined(__clang__) return __PRETTY_FUNCTION__; #endif } int main() { std::cout << type_name<int>(); }输出分别为:
// gcc consteval auto type_name() [with T = int] // clang auto type_name() [T = int] // msvc auto __cdecl type_name<int>(void)gcc的这种格式不错,clang丢弃了consteval,msvc同样如此,但它加上了函数调用约定。 现在需要做的,就是根据这些信息,解析出想要的信息。可以借助C++17 std::string_view在编译期完成这个工作。具体实现如下。
template <typename T> consteval auto type_name() { std::string_view name, prefix, suffix; #ifdef __clang__ name = __PRETTY_FUNCTION__; prefix = "auto type_name() [T = "; suffix = "]"; #elif defined(__GNUC__) name = __PRETTY_FUNCTION__; prefix = "consteval auto type_name() [with T = "; suffix = "]"; #elif defined(_MSC_VER) name = __FUNCSIG__; prefix = "auto __cdecl type_name<"; suffix = ">(void)"; #endif name.remove_prefix(prefix.size()); name.remove_suffix(suffix.size()); return name; }通过使用std::string_view,以上代码全都发生于编译期。该代码来自https://stackoverflow.com/a/56766138。这个实现方式要比Demanged Name好,不会丢失修饰,类型信息完善,且发生于编译期。缺点也有,编译器扩展一般都是基于实现的,没有标准保证,内容形式可能会改变,依赖于此的实现并不具备较强的稳定性。
template <typename... Ts> void print_types() { printf("%d - %s ", int..., Ts.string)...; } print_types<int, double, const char*, int&&>(); // output: // 0 - int // 1 - double // 2 - const char* // 3 - int&&是不是太简单了!而且还要强大许多,比如还可以去重、排序:
template <typename... Ts> void f() { printf("unique: "); print_types
template <typename T> consteval auto type_name() { return meta::name_of(reflexpr(T)); } int main() { const int i = 1; constexpr auto __dummy = __reflect_print(type_name<decltype(i)>()); }这里,将在编译期输出const int。虽然标准反射目前来说还是一个残缺品,但实现这种需求也比自己实现起来要简单太多了。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !