如何在C++中获得完整的类型名称

如题所述

地球人都知道C++里有一个typeid操作符可以用来获取一个类型/表达式的名称:
std::cout << typeid(int).name() << std::endl;

但是这个name()的返回值是
取决于编译器的
,在vc和gcc中打印出来的结果如下:

int // vc
i // gcc

一个稍微长一点的类型名称,比如:

class Foo {};
std::cout << typeid(Foo*[10]).name() << std::endl;

打出来是这个效果:

class Foo * [10] // vc
A10_P3Foo // gcc

(话说gcc您的返回结果真是。。)

当然了,想在gcc里得到和微软差不多显示效果的方法也是有的,那就是使用
__cxa_demangle
:

char* name = abi::__cxa_demangle(typeid(Foo*[10]).name(), nullptr, nullptr, nullptr);
std::cout << name << std::endl;
free(name);

显示效果:

Foo* [10]

先不说不同编译器下的适配问题,来看看下面这个会打印出啥:

// vc
std::cout << typeid(const int&).name() << std::endl;

// gcc
char* name = abi::__cxa_demangle(typeid(const int&).name(), nullptr, nullptr, nullptr);
std::cout << name << std::endl;
free(name);

显示效果:

int // vc
int // gcc

可爱的cv限定符和引用都被丢掉了=.=

如果直接在typeid的结果上加上被丢弃的信息,对于一些类型而言(如函数指针引用)得到的将不是一个正确的类型名称。

想要获得一个类型的完整名称,并且获得的名称必须要是一个正确的类型名称,应该怎样做呢?

一、如何检查C++中的类型

我们需要一个泛型类,用特化/偏特化机制静态检查出C++中的各种类型,并且不能忽略掉类型限定符(type-specifiers)和各种声明符(declarators)。

先来考虑一个最简单的类模板:
template <typename T>
struct check
{
// ...
};

假如在它的基础上特化,需要写多少个版本呢?我们可以稍微实现下试试:

template <typename T> struct check<T &>;
template <typename T> struct check<T const &>;
template <typename T> struct check<T volatile &>;
template <typename T> struct check<T const volatile &>;

template <typename T> struct check<T &&>;
template <typename T> struct check<T const &&>;
template <typename T> struct check<T volatile &&>;
template <typename T> struct check<T const volatile &&>;

template <typename T> struct check<T *>;
template <typename T> struct check<T const *>;
template <typename T> struct check<T volatile *>;
template <typename T> struct check<T const volatile *>;
template <typename T> struct check<T * const>;
template <typename T> struct check<T * volatile>;
template <typename T> struct check<T * const volatile>;

template <typename T> struct check<T []>;
template <typename T> struct check<T const []>;
template <typename T> struct check<T volatile []>;
template <typename T> struct check<T const volatile []>;
template <typename T, size_t N> struct check<T [N]>;
template <typename T, size_t N> struct check<T const [N]>;
template <typename T, size_t N> struct check<T volatile [N]>;
template <typename T, size_t N> struct check<T const volatile [N]>;

// ......

这还远远没有完。有同学可能会说了,我们不是有伟大的宏嘛,这些东西都像是一个模子刻出来的,弄一个宏批量生成下不就完了。

实际上当我们真的信心满满的动手去写这些宏的时候,才发现适配上的细微差别会让宏写得非常痛苦(比如&和*的差别,[]和[N]的差
别,还有函数类型、函数指针、函数指针引用、函数指针数组、类成员指针、……)。当我们一一罗列出需要特化的细节时,不由得感叹C++类型系统的复杂和纠
结。

但是上面的理由并不是这个思路的致命伤。

不可行的地方在于:我们可以写一个多维指针,或多维数组,类型是可以嵌套的。总不可能为每一个维度都特化一个模板吧。

不过正由于类型其实是嵌套的,我们可以用模板元编程的基本思路来搞定这个问题:
template <typename T> struct check<T const> : check<T>;
template <typename T> struct check<T volatile> : check<T>;
template <typename T> struct check<T const volatile> : check<T>;

template <typename T> struct check<T & > : check<T>;
template <typename T> struct check<T &&> : check<T>;
template <typename T> struct check<T * > : check<T>;

// ......

一个简单的继承,就让特化变得simple很多。因为当我们萃取出一个类型,比如T *,之后的T其实是携带上了除*之外所有其他类型信息的一个类型。那么把这个T再重复投入check中,就会继续萃取它的下一个类型特征。

可以先用指针、引用的萃取来看看效果:
#include <iostream>

template <typename T>
struct check
{
check(void) { std::cout << typeid(T).name(); }
~check(void) { std::cout << std::endl; }
};

#define CHECK_TYPE__(OPT) \
template <typename T> \
struct check<T OPT> : check<T> \
{ \
check(void) { std::cout << " "#OPT; } \
};

CHECK_TYPE__(const)
CHECK_TYPE__(volatile)
CHECK_TYPE__(const volatile)
CHECK_TYPE__(&)
CHECK_TYPE__(&&)
CHECK_TYPE__(*)

int main(void)
{
check<const volatile void * const*&>();
system("pause");
return 0;
}

输出结果(vc):

void const volatile * const * &

很漂亮,是不是?当然,在gcc里这样输出,void会变成v,所以gcc下面要这样写check模板:

template <typename T>
struct check
{
check(void)
{
char* real_name = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
std::cout << real_name;
free(real_name);
}
~check(void) { std::cout << std::endl; }
};

二、保存和输出字符串

我们可以简单的这样修改check让它同时支持vc和gcc:
template <typename T>
struct check
{
check(void)
{
# if defined(__GNUC__)
char* real_name = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
std::cout << real_name;
free(real_name);
# else
std::cout << typeid(T).name();
# endif
}
~check(void) { std::cout << std::endl; }
};

但是到目前为止,check的输出结果都是无法保存的。比较好的方式是可以像typeid(T).name()一样返回一个字符串。这就要求check能够把结果保存在一个std::string对象里。

当然了,我们可以直接给check一个“std::string&
out”类型的构造函数,但是这样会把输出的状态管理、字符的打印逻辑等等都揉在一起。因此,比较好的设计方法是实现一个output类,负责输出和维护
状态。我们到后面就会慢慢感觉到这样做的好处在哪里。

output类的实现可以是这样:

class output
{
bool is_compact_ = true;

template <typename T>
bool check_empty(const T&) { return false; }
bool check_empty(const char* val)
{
return (!val) || (val[0] == 0);
}

template <typename T>
void out(const T& val)
{
if (check_empty(val)) return;
if (!is_compact_) sr_ += " ";
using ss_t = std::ostringstream;
sr_ += static_cast<ss_t&>(ss_t() << val).str();
is_compact_ = false;
}

std::string& sr_;

public:
output(std::string& sr) : sr_(sr) {}

output& operator()(void) { return (*this); }

template <typename T1, typename... T>
output& operator()(const T1& val, const T&... args)
{
out(val);
return operator()(args...);
}

output& compact(void)
{
is_compact_ = true;
return (*this);
}
};

这个小巧的output类负责自动管理输出状态(是否增加空格)和输出的类型转换(使用std::ostringstream)。

上面的实现里有两个比较有意思的地方。

一是operator()的做法,采用了变参模板。这种做法让我们可以这样用output:

output out(str);
out("Hello", "World", 123, "!");

这种写法比cout的流操作符舒服多了。

二是operator()和compact的返回值。当然,这里可以直接使用void,但是这会造成一些限制。

比如说,我们想在使用operator()之后马上compact呢?若让函数返回自身对象的引用,就可以让output用起来非常顺手:

output out(str);
out.compact()("Hello", "World", 123, "!").compact()("?");

check的定义和CHECK_TYPE__宏只需要略作修改就可以使用output类:

template <typename T>
struct check
{
output out_;
check(const output& out) : out_(out)
{
# if defined(__GNUC__)
char* real_name = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
out_(real_name);
free(real_name);
# else
out_(typeid(T).name());
# endif
}
};

#define CHECK_TYPE__(OPT) \
template <typename T> \
struct check<T OPT> : check<T> \
{ \
using base_t = check<T>; \
using base_t::out_; \
check(const output& out) : base_t(out) { out_(#OPT); } \
};

为了让外部的使用依旧简洁,实现一个外敷函数模板是很自然的事情:

template <typename T>
inline std::string check_type(void)
{
std::string str;
check<T> { str };
return std::move(str);
}

int main(void)
{
std::cout << check_type<const volatile void * const*&>() << std::endl;
system("pause");
return 0;
}
温馨提示:内容为网友见解,仅供参考
无其他回答

如何在C++中获得完整的类型名称
如果直接在typeid的结果上加上被丢弃的信息,对于一些类型而言(如函数指针引用)得到的将不是一个正确的类型名称。想要获得一个类型的完整名称,并且获得的名称必须要是一个正确的类型名称,应该怎样做呢?一、如何检查C++中的类型我们需要一个泛型类,用特化\/偏特化机制静态检查出C++中的各种类型,并且不能忽略掉类型限定...

C++ 中的typeid().name()只输出一个字符是怎么回事?
gcc就是那样的,只输出类型名的第一个字符,要输出完整的名字可以这样:include <iostream> include <typeinfo> include <cxxabi.h> \/\/使用abi using namespace std;int main(){ cout<<abi::__cxa_demangle(typeid(int).name(),0,0,0 )<<endl;return 0;} ...

C++中如何把过长的double类型以完整的形式显示呢?
有格式设置的。如果你用cout输出,用这个设置:cout <<setprecision(n)n是有效数字,你要显示20位,n就是20 如果你用printf 比方输出a变量 printf("%m.nf",a);m是总宽度,你可以设置大一些,n是小数位数,设置个3或者6

c++命名规范中,int[]以什么开头??
三、 类的成员变量以m_开头,后面为变量,变量同时还要加前缀。CString m_strName; \/\/m_开头+类型前缀+名称 四、 定义一个变量,为了简化,在不影响变量意义的情况下,可仅仅使用前缀。RECT rc;五、 全局变量一律以g_开头,后面为变量,变量同时还要加前缀。int g_ID; \/...

c++中的工程类型问题
\/\/ 先获得当前工作目录的全路径 ―getcwd(path_search,―MAX―PATH); \/\/ 再获得文件的完整的路径名(包含文件的名称) strcat(path_search,〃\\\\〃); strcat(path―search,filestruct.name); MessageBox(path_search); \/\/输出显示 } } \/\/ 继续对当前目录中的下一个子目录或文件进行与上面同样的查找 whil...

C++报错E0852:表达式必须是指向完整对象类型的指针?
c++多文件编程,每有一个自定义cpp文件,应对应一个.h头文件 在使用其他cpp文件中的类时,需要引入头文件 文件导入时,应该目录对应,如src下的目录,应该下 #include<src\/XXX.h> 如果不做目录的指明,可以通过配置编译参数来指定明路,否则会找不到 如果出现了重名,会报编译错误,因为存在二义性 ...

C++代码输入中出现不允许出现不完整的类型
你在CSceneView类定义之前,只是声明了CSceneList类,而没有定义它,例如:class CSceneList;class CSceneView { ... CSceneList list1; \/\/ 前面的 CSceneList 类只有声明而没有定义,是“不完整的类型”

C++的类型(type)
在C++中,cv qualifiers是用于指定对象的constness和volatility的修饰符,可以出现在任何类型说明符中。cv qualifiers有三种形式:const、volatile和const volatile。mutable是一种修饰符,只能用于类的成员,表示即使类是const的,该成员也可以被修改。cv void表示可能的cv qualified的void类型,实质上有四种情况...

UE5 C++中获取在蓝图中创建的DataTable和Struct的RowData
常规做法中,通过DataTable的FindRowUnchecked函数根据RowName获取Row指针,进而使用GetRowStruct函数获取蓝图中Struct的C++父类。然后,通过内部的PropertyLink迭代属性,从而获取所有属性名称。接下来,根据Class进行if判断,以识别当前属性值为声明类型。实现这一过程的关键代码如下:代码示例:通过反射机制获取Data...

c++不允许使用不完整的类型
定义对象之前,编译器必须看到完整的类定义,你的代码istringstream str( str ) 中的istringstream只是声明了,还没有定义,需要包含头文件#include <sstream>。

相似回答