You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:27:09