1. RTTI的实现
如何获取一个类型T的name或者唯一ID, 对于这个问题, 最常规的方式应该是借助C++的rtti了, 比如如ponder中所使用的方式:
namespace ponder {namespace detail {typedef std::type_index TypeId; // Used to uniquely identify a type.// Calculate TypeId for Ttemplate <typename T>inline TypeId calcTypeId() {return TypeId(typeid(T)); }}}
其中的TypeID其实就是std::type_index, ponder中大量依赖这种方式进行 T -> std::type_index的转换, 作为常规实现, 它具有以下特点:
它确实能够比较好的生成一个自带hash实现的std::type_index, 可以很好的用它作为key来索引类型相关的对象.
利用相同的方法也可以很容易的获取类型T的字符串名称.
在rtti开启的情况下, 功能直接可获取.
但不难发现, 这种方法也存在以下问题:
首先是依赖rtti, 在一些rtti关闭的情况下, 对应机制就停摆了
很重要的一点, 这是一个runtime实现, 也就是说, 天生与constexpr无缘, 我们没有办法做任何善于compiler time的优化.
std::type_index的实现一言难尽, 虽然你可以通过hash获取一个size_t类型的ID, 但对于这个值的稳定性和有效性都依赖于库底层的实现, 当一些特定的场合需要跨App进行ID标识的时候, 它基本就没啥用了, 你只能退回字符串名称的方式去处理相关逻辑了.
2. FUNCSIG系统宏的实现
那么有没有一种实现是不依赖rtti, 又能很好的适配constexpr的呢? 答案是肯定的. 比如decs中对类型T的hash()方式:
struct MetatypeHash {size_t name_hash{ 0 };size_t matcher_hash{ 0 };bool operator==(const MetatypeHash& other)const {return name_hash == other.name_hash;}template<typename T>static constexpr const char* name_detail() {return __FUNCSIG__;}template<typename T>static constexpr size_t hash() {static_assert(!std::is_reference_v<T>, "dont send references to hash");static_assert(!std::is_const_v<T>, "dont send const to hash");return hash_fnv1a(name_detail<T>());}};
如代码所示, 很好的解决了compiler time生成name_detail()和hash()的问题, hash这个地方使用了一个hash_fnv1a()的算法, 不是本文关注的重点, 这里不展开了. 通过constexpr, 很好的解决了编译期获取类型T的唯一字符名称和64位hash的问题, 而且不依赖rtti, 比较优雅的解决了T->name和T->id的问题.
当然, 上面的代码其实没有解决跨平台的问题, 另外对某一类型T, 获取到的其实是类似 "const char *_cdecl ecs::MetatypeHash::namedetail
struct MetatypeHash {size_t name_hash{ 0 };size_t matcher_hash{ 0 };bool operator==(const MetatypeHash& other)const {return name_hash == other.name_hash;}template<typename T>static constexpr const char* name_detail() {#if RSTUDIO_CORE_PLATFORM == RSTUDIO_CORE_PLATFORM_LINUXreturn __PRETTY_FUNCTION__;#elsereturn __FUNCSIG__;#endif}#if RSTUDIO_CORE_PLATFORM != RSTUDIO_CORE_PLATFORM_WIN32template<typename T>static constexpr auto name_pretty(){//name_detail() is like: static constexpr const char* ecs::MetatypeHash::name_detail() [with T = rstudio::math::Vector3]std::string_view pretty_name = name_detail<T>();std::string_view prefix = "static constexpr const char* ecs::MetatypeHash::name_detail() [with T = ";std::string_view suffix = "]";pretty_name.remove_prefix(prefix.size());pretty_name.remove_suffix(suffix.size());return pretty_name;}#elsetemplate<typename T>static constexpr auto name_pretty(){//name_detail() is like "const char *__cdecl ecs::MetatypeHash::name_detail<class rstudio::math::Vector3>(void)"std::string_view pretty_name = name_detail<T>();std::string_view prefix = "const char *__cdecl ecs::MetatypeHash::name_detail<";std::string_view suffix = ">(void)";pretty_name.remove_prefix(prefix.size());pretty_name.remove_suffix(suffix.size());size_t start_of = pretty_name.find_first_of(' ') + 1;return pretty_name.substr(start_of);}#endiftemplate<typename T>static constexpr size_t hash() {static_assert(!std::is_reference_v<T>, "dont send references to hash");static_assert(!std::is_const_v<T>, "dont send const to hash");return hash_fnv1a(name_detail<T>());}};
这样一些不需要友好名字的地方我们可以使用name_detail(), 一些需要友好名字的地方, 我们使用name_pretty(), 至于hash(), 看自己的使用场合, 可以灵活选用name_detail()或者name_pretty()了, 以上实现在VS2019和GCC8.3上测试过, clang的话需要处理一下, 宏跟Linux下一样都是 PRETTY_FUNCTION, 但前缀会稍有区别, 对应的name_pretty()实现需要做下调整, 手边没有安装Clang的机器, 就先不贴出适配Clang的版本了.
这种compiler time的优化对于支持C++17特性的编译器来说, 还是值得去做的, 一些高频操作, 参与Deduce的类型又比较可预估的时候, 我们肯定会倾向于用更多的compiler time实现来取代像前文提到的rtti这种runtime 实现了.




