如何用C++宏实现自定义typeid,区分类型与值参数?
当然可以实现!不过得结合C的编译期特性(比如C20的requires表达式,或者更早版本的SFINAE)来让编译器自动区分传入的是类型还是值,下面我给你一步步拆解方案:
实现核心思路
宏本身在预处理阶段没法区分类型和值,但我们可以把判断逻辑交给编译器——通过模板重载、编译期检查,让编译器自动选择对应的my_type_info调用方式:
- 如果传入的
token是类型,就调用模板版本my_type_info<token>() - 如果传入的
token是值,就调用函数版本my_type_info(token)
具体实现(C++20+,最简洁可靠)
首先我们先定义两个版本的my_type_info,分别处理类型和值:
#include <typeinfo> // 处理类型:传入类型T,返回对应的类型信息 template<typename T> auto my_type_info() { // 这里可以替换成你自定义的类型信息逻辑 return typeid(T).name(); } // 处理值:传入任意值,返回该值的类型信息 template<typename T> auto my_type_info(const T& value) { // 同样可以自定义逻辑 return typeid(value).name(); }
然后是核心的MY_TYPEID宏,利用C++20的constexpr requires做编译期判断:
#define MY_TYPEID(token) \ []() -> decltype(auto) { \ // 编译期检查:如果token能作为模板参数传入my_type_info<>(),说明是类型 if constexpr (requires { my_type_info<token>(); }) { return my_type_info<token>(); } else { // 否则当作值处理,直接传入函数 return my_type_info(token); } \ }()
测试用例
我们来验证一下这个宏的效果:
#include <iostream> #include <vector> int main() { int num = 42; double pi = 3.14; // 传入类型 std::cout << "Type of int: " << MY_TYPEID(int) << "\n"; std::cout << "Type of std::vector<int>: " << MY_TYPEID(std::vector<int>) << "\n"; // 传入值 std::cout << "Type of num: " << MY_TYPEID(num) << "\n"; std::cout << "Type of literal 3.14: " << MY_TYPEID(3.14) << "\n"; std::cout << "Type of pi: " << MY_TYPEID(pi) << "\n"; return 0; }
运行后会输出对应类型的名称(具体格式取决于编译器,比如GCC会输出简化的类型名)。
自定义类型信息扩展
如果不想用标准库的typeid,可以完全自定义my_type_info的逻辑,比如返回包含类型名和大小的结构体:
struct CustomTypeInfo { const char* type_name; size_t type_size; }; template<typename T> CustomTypeInfo my_type_info() { return { typeid(T).name(), sizeof(T) }; } template<typename T> CustomTypeInfo my_type_info(const T& value) { return { typeid(value).name(), sizeof(value) }; }
这样MY_TYPEID就会返回你自定义的类型信息结构体了。
C++17及以下的兼容方案
如果你的项目还在用C++17或更早版本,可以用SFINAE(替换失败不是错误)来实现类似的逻辑,只是代码稍微繁琐一点:
#include <typeinfo> #include <type_traits> #include <utility> namespace detail { // 编译期检查:判断T是否能作为模板参数传入my_type_info<>() template<typename T, typename = void> struct is_type_callable : std::false_type {}; template<typename T> struct is_type_callable<T, std::void_t<decltype(my_type_info<T>())>> : std::true_type {}; // 类型版本的调用 template<typename T> std::enable_if_t<is_type_callable<T>::value, decltype(my_type_info<T>())> invoke_my_type_info() { return my_type_info<T>(); } // 值版本的调用 template<typename T> std::enable_if_t<!is_type_callable<T>::value, decltype(my_type_info(std::declval<T>()))> invoke_my_type_info(T&& val) { return my_type_info(std::forward<T>(val)); } } // 宏的实现:通过编译期判断选择调用方式 #define MY_TYPEID(token) \ []() -> decltype(auto) { \ using TokenType = decltype((token)); \ if constexpr (detail::is_type_callable<TokenType>::value) { \ return detail::invoke_my_type_info<TokenType>(); \ } else { \ return detail::invoke_my_type_info(std::forward<TokenType>(token)); \ } \ }()
不过要注意,这种方式对于一些特殊类型(比如CV限定的类型)可能需要额外调整,而C++20的requires表达式会更直观可靠。
注意事项
- 避免同时存在同名的类型和变量:如果你的代码里有一个类型叫
foo,同时又有一个变量叫foo,宏会优先匹配类型版本,甚至可能导致编译歧义,这种情况尽量避免。 - 模板类型支持:对于
std::map<int, std::string>这类复杂模板类型,宏也能正确识别为类型。
内容的提问来源于stack exchange,提问作者user9118363
相关产品推荐
相关产品推荐

