大家应该比较熟悉, C++中可以通过 操作符 typeid来获取类型名称
std::cout << typeid(int).name() << std::endl;
但是这个name()的返回值是取决于编译器的,在vc和gcc中打印出来的结果如下:
int // vc
i // gcc
对于自定义类,输出的类型,也并不是原始类型
class TestType {};
std::cout << typeid(TestType).name() << std::endl;
//vs输出 class TestType
因此, 操作符 typeid的作用就局限于类型输出及类型与类型进行对比, 并不能用于跟字符串对比。
下面我们再看看,容器类型的输出:
std::cout << typeid(std::map<std::string, int>).name() << std::endl;
以下将自定义类型处理,对typeid进行封装,简化类型的输出
封装typeid,统一平台输出
template<typename T>
std::string getTypeName()
{
std::string tyName;
#if defined(__GNUC__)
char* real_name = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
tyName = real_name;
free(real_name);
#else
tyName = typeid(T).name();
#endif
return tyName;
}
定义类型处理模板
//非特化流程,进行自动化萃取分类
template<typename T>
struct type_system
{
std::string name()
{
return type_tarits<T>().name();
}
};
std::string 处理
// 支持std::string
template<>
struct type_system<std::string>
{
std::string name()
{
return "std::string";
}
};
单个参数的容器类型处理
// 支持一个参数的STL容器
#define __TYPE_SYSTEM_DEF_ONE(OPT) \
template<typename T> \
struct type_system< OPT<T> > \
{ \
std::string name() \
{\
std::ostringstream oss;\
oss << #OPT << "<" << type_system<T>().name() << ">";\
return oss.str();\
}\
};
__TYPE_SYSTEM_DEF_ONE(std::vector)
两个个参数的容器类型处理
// 支持两个参数的STL容器
#define __TYPE_SYSTEM_DEF_TWO(OPT) \
template<typename K, typename V> \
struct type_system< OPT<K, V> >\
{\
std::string name()\
{\
std::ostringstream oss;\
oss << #OPT << "<" << type_system<K>().name() << "," << type_system<V>().name() << ">";\
return oss.str();\
}\
};
__TYPE_SYSTEM_DEF_TWO(std::map)
支持std::tuple输出
struct TupleArgs
{
template<typename Tuple, std::size_t... I>
static void Get(std::ostringstream& oss, std::index_sequence<I...>)
{
std::vector<std::string> paramType{ _Get<typename std::tuple_element<I, Tuple>::type>()... };
for (size_t i = 0; i < paramType.size(); i++)
{
oss << paramType[i];
if (i != paramType.size() - 1)
{
oss << ",";
}
}
}
template<typename T>
static std::string _Get()
{
return type_system<T>().name();
}
};
// 支持std::tuple
template<typename...Args>
struct type_system< std::tuple<Args...> >
{
std::string name()
{
std::ostringstream oss;
oss << "std::tuple" << "<";
TupleArgs::Get<std::tuple<Args...>>(oss, std::make_index_sequence<sizeof...(Args)>{});
oss << ">";
return oss.str();
}
};
支持函数指针
// 支持函数指针
template<typename R, typename...Args>
struct type_system< R (*)(Args...) >
{
std::string name()
{
std::ostringstream oss;
oss << type_system<R>().name() << "(*)(";
TupleArgs::Get<std::tuple<Args...>>(oss, std::make_index_sequence<sizeof...(Args)>{});
oss << ")";
return oss.str();
}
};
支持成员变量
// 支持成员变量
template<typename T, typename C>
struct type_system<T C::* >
{
std::string name()
{
std::ostringstream oss;
oss << type_system<T>().name() << " " << type_system<C>().name() << "::*";
return oss.str();
}
};
支持成员函数指针
template<typename R, typename C, typename...Args>
struct type_system< R(C::*)(Args...) >
{
std::string name()
{
std::ostringstream oss;
oss << type_system<R>().name() << "(" << type_system<C>().name() << "::*)(";
TupleArgs::Get<std::tuple<Args...>>(oss, std::make_index_sequence<sizeof...(Args)>{});
oss << ")";
return oss.str();
}
};
出了特化类型,还有基础类型和自定义类型,需要进一步区分处理
//类型系统
template<typename T, typename P>
struct __type_system;
//非类类型
template<typename T >
struct __type_system<T, typename std::enable_if<!std::is_class<T>::value, T>::type>
{
std::string name()
{
return getTypeName<T>();
}
};
//类类型
template<typename T >
struct __type_system<T, typename std::enable_if<std::is_class<T>::value, T>::type>
{
std::string name()
{
std::string tmpName = getTypeName<T>();
int nPos = tmpName.find("class ");
if (nPos != std::string::npos)
{
return tmpName.substr(nPos + 6);
}
return tmpName;
}
};
template<typename T>
class type_tarits : public __type_system< T, T>
{
public:
};
以上对所有类型进行分区分处理,接下来定义一个普通的函数模板来进行简化使用,支持参数值和参数类型两种方式的类型获取
template<typename T>
std::string type_name(T val)
{
return std::move(type_system<T>().name());
}
template<typename T>
std::string type_name()
{
return std::move(type_system<T>().name());
}
接下来测试一下,输出的效果:
int main()
{
std::cout << type_name<int>() << std::endl;
std::cout << type_name<std::map<std::string, std::string>>() << std::endl;
std::cout << type_name<std::map<std::string, std::vector<std::string>>>() << std::endl;
std::cout << type_name<Test>() << std::endl;
std::unordered_map<std::string, std::string> v1;
std::tuple<std::string, std::string, int, double> t1;
std::cout << type_name(v1) << std::endl;
std::cout << type_name(t1) << std::endl;
std::cout << type_name(&FuncTest) << std::endl;
std::cout << type_name(&Test::Func) << std::endl;
std::cout << type_name(&Test::a) << std::endl;
return 0;
}
总结:
因为项目需要对类型进行反向输出,因为暂停下来研究了一下类型系统,还是挺有意思的,以上内容大量使用了泛型编程技巧, 充分运用了C++在预处理期、编译期和运行期(RAII)的处理能力 ,在此基础上,我们可以进行进步一细化处理,也可以对不同平台进行字解析处理,已达到统一平台的目的。
或许你会问,研究这些有什么用,其实这里还真不是兴起研究玩玩的,我的目的是在网络编程中,可以通过服务端自解析的方式输出客户端代码,已保证客户端与服务端的接口一致性,也简化开发流程。
目前RPC框架中,大部分都是通过定义接口文件,然后通过接口解析程序生成服务端接口和客户端接口,但服务端接口代码继承到服务中也是非常繁琐,如果服务端能自解析生成客户端代码,那么我们面对网络编程时就像开发单机程序一样简单,这是一件多么令人兴奋的事情啊!
来源:oschina
链接:https://my.oschina.net/u/3312209/blog/4311026