C++模板元编程编译期推断

C++模板元编程编译期推断

编译期函数特征推断


template<typename T>
struct function_traits;

模板类 function_traits,用于获取函数类型(包括普通函数、成员函数等)的相关信息,如返回类型、参数数量和参数类型。可在编译时进行元编程和类型检查。
通过特化版本可实现对不同类型的函数类型的处理。

普通函数

// 普通函数
template<typename ReturnType, typename... Args>
struct function_traits<ReturnType(Args...)>
{
    enum { arity = sizeof...(Args) };
    using return_type = ReturnType;
    using function_type = ReturnType(Args...);
    using stl_function_type = std::function<function_type>;
    using pointer = ReturnType(*)(Args...);

    template<size_t I>
    struct args
    {
        static_assert(I < arity, "index is out of range, index must less than sizeof Args");
        using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
    };

    using tuple_type = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...>;
    using bare_tuple_type = std::tuple<std::remove_const_t<std::remove_reference_t<Args>>...>;
};
  • template<typename ReturnType, typename... Args>:针对形如 ReturnType(Args...) 的函数类型进行特化,其中 ReturnType 是返回类型,Args 是可变参数模板,表示零个或多个参数类型。
  1. 特化版中定义了以下成员:

    • enum { arity = sizeof...(Args) };:计算并存储参数数量(arity)。

    • using return_type = ReturnType;:定义 return_type 为函数的返回类型。

    • using function_type = ReturnType(Args...);:定义 function_type 为原始函数类型。

    • using stl_function_type = std::function<function_type>;:定义 stl_function_type 为与原始函数类型兼容的 std::function 类型。

    • using pointer = ReturnType(*)(Args...);:定义 pointer 为指向该类型函数的指针。

  2. template<size_t I> struct args:定义一个内部模板类 args,用于获取第 I 个参数的类型。通过 static_assert 确保索引有效,并使用 std::tuple_elementstd::tuple<Args...> 获取对应参数类型。

  3. using tuple_type = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...>;:创建一个元组(tuple),包含所有参数类型(去除引用和cv限定符)。

  4. using bare_tuple_type = std::tuple<std::remove_const_t<std::remove_reference_t<Args>>...>;:类似于 tuple_type,但仅去除 const 限定符,保留 volatile 限定符(如果存在)。

使用:

void add(int a, std::string b, float c){
    return ;
}

int main() {
    using MyFunctionTraits = function_traits<decltype(add)>;
    // 返回类型
    std::cout << typeid(MyFunctionTraits::return_type).name() << std::endl;  // 输出 void

    // 参数数量
    std::cout << MyFunctionTraits::arity << std::endl;  // 输出 3

    // 第一个参数类型
    using FirstParamType = typename MyFunctionTraits::template args<0>::type;
    std::cout << typeid(FirstParamType).name() << std::endl;  // 输出 int

    // 元组类型
    std::cout << typeid(MyFunctionTraits::tuple_type).name() << std::endl; 
    // 输出 std::tuple<int, class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>>, float>

    // 去除const限定符的元组类型
    std::cout << typeid(MyFunctionTraits::bare_tuple_type).name() << std::endl; 
    // 输出一样的结果,因为这里的参数类型没有const限定
    return 0;
}

函数指针

// 函数指针
template<typename ReturnType, typename... Args>
struct function_traits<ReturnType(*)(Args...)> : function_traits<ReturnType(Args...)> {};

当模板参数是一个函数指针类型(如 ReturnType(*)(Args...))时,function_traits 的行为。它继承自普通函数版本的 function_traits<ReturnType(Args...)>,这意味着函数指针的所有特性(如返回类型、参数数量和类型等)都按照普通函数的方式来提取。

std::function

// std::function
template<typename ReturnType, typename... Args>
struct function_traits<std::function<ReturnType(Args...)>> : function_traits<ReturnType(Args...)> {};

尽管 std::function 可以存储各种可调用对象(函数、函数指针、lambda 表达式、成员函数指针等),但在类型层面,它仍然被视为具有特定签名的实体。因此,这里的 function_traits 特化同样继承自普通函数版本,以提取封装在其内的函数特性。

成员函数

// 成员函数
#define FUNCTION_TRAITS(...)\
template <typename ReturnType, typename ClassType, typename... Args>\
struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : function_traits<ReturnType(Args...)>{};\

FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)

利用宏定义处理成员函数的特性。这里的 __VA_ARGS__ 是可变参数占位符,允许接收成员函数的任意 cv 限定符和 ref-qualifier(如 constvolatile&&& 等)。该特化表明成员函数的 function_traits 继承自相应的非成员函数版本,忽略了类类型 ClassType,重点关注函数的返回类型和参数列表。

通过 __VA_ARGS__ 来捕获任何额外的cv限定符和其他可能的ref-qualifier。例如,当使用宏 FUNCTION_TRAITS(const) 时,实际上会生成这样的结构:

template <typename ReturnType, typename ClassType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> : function_traits<ReturnType(Args...)> {};

同样地,FUNCTION_TRAITS(volatile)FUNCTION_TRAITS(const volatile) 分别生成:

template <typename ReturnType, typename ClassType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) volatile> : function_traits<ReturnType(Args...)> {};

template <typename ReturnType, typename ClassType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const volatile> : function_traits<ReturnType(Args...)> {};

这样就可以处理成员函数的各种cv限定符组合,而无需分别为每种情况显式编写特化版本。在处理成员函数时,function_traits 关注的是函数本身的签名(返回类型和参数类型),而非类类型或cv限定符,因此这里都继承自普通函数签名的 function_traits 版本。

函数对象

// 函数对象
template<typename Callable>
struct function_traits : function_traits<decltype(&Callable::operator())> {};

模板 function_traits 的这个实现是用来获取任意可调用对象(包括函数、函数指针、lambda 表达式、重载了 operator() 的类实例等)的元信息。对于函数对象,我们通常关注其 operator() 的签名来推断其行为特征。

这里的模板定义首先尝试获取 Callable 类型上的 operator() 成员函数的地址,并以其类型作为 decltype(&Callable::operator()) 进行进一步处理。这种做法利用了模板递归的特性,因为对于一个函数对象,它的 operator() 就像一个普通的函数一样,具有返回类型和参数列表。

通过这种方式,我们可以提取出 Callable 类型的 operator() 的相关信息,比如返回类型、参数类型等,进而封装到 function_traits 结构体中。这个设计通常会有一个基础特化版本来处理不同类型的可调用对象,特别是非成员函数和特定形式的成员函数指针,以便于提取出它们的特征。

转换

template<typename Function>
typename function_traits<Function>::stl_function_type to_function(const Function& lambda)
{
    return static_cast<typename function_traits<Function>::stl_function_type>(lambda);
}

template<typename Function>
typename function_traits<Function>::stl_function_type to_function(Function&& lambda)
{
    return static_cast<typename function_traits<Function>::stl_function_type>(std::forward<Function>(lambda));
}

template<typename Function>
typename function_traits<Function>::pointer to_function_pointer(const Function& lambda)
{
    return static_cast<typename function_traits<Function>::pointer>(lambda);
}

这段代码提供了两个模板函数 to_function 和一个模板函数 to_function_pointer,它们分别用于将不同的可调用对象(如 Lambda 表达式或其他函数对象)转换为对应的 std::function 类型和函数指针类型。

  1. to_function 函数模板:

    • 接受一个 const 引用或右值引用 Function 类型的参数 lambda
    • 利用 function_traits 模板类获取 Function 类型的 stl_function_type,这是一个与 Function 兼容的 std::function 类型。
    • 使用 static_castlambda 转换为 stl_function_type 类型并返回。
  2. to_function_pointer 函数模板:

    • 接受一个 const 引用的 Function 类型参数 lambda
    • 利用 function_traits 模板类获取 Function 类型的 pointer 类型,这是一个指向与 Function 符合相同签名的函数指针类型。
    • 使用 static_castlambda 转换为 pointer 类型并返回。

需要注意的是,不是所有的可调用对象都能安全地转换为函数指针或 std::function。例如,Lambda 表达式(尤其是那些捕获了环境变量的 Lambda)无法直接转换为函数指针。另外,std::function 会带来一些运行时开销,不适用于对性能要求较高的场景。因此,在实际使用中,应确保转换操作合法且符合预期。

编译器类特征推断

计算类成员数量

template <typename T>
constexpr std::size_t MembersCount = 0;

// 万能类型,可以隐式转换成任意类
struct UniversalType {
    template <typename T>
    operator T();
};

template <typename T, typename... Args>
consteval std::size_t MemberCountImpl() {
    if constexpr (requires {
        T {
            {Args{}}...,
            {UniversalType{}}
        };
    }) {
        return MemberCountImpl<T, Args..., UniversalType>();
    } else {
        return sizeof...(Args);
    }
}

template <typename T>
consteval std::size_t MemberCount() {
    // 说明用户已经特化该类的成员数量,直接返回
    if constexpr (MembersCount<T> > 0) {
        return MembersCount<T>;
    } else {
        return MemberCountImpl<T>();
    }
}

计算类成员数量的模板函数 MemberCount。这里主要思路是利用 C++20 的 consteval(常量评估函数)和 if constexpr 结合 requires 语句来进行类型检查和递归计算。

要想理解这个函数必须先理解这个 concept 约束了什么。
这里涉及到了一个特性

struct Foo {
    int a;
    int b;
    int c;
};

对于一个聚合类 Foo,以下初始化方法都是合法的

Foo a{1};
Foo a{1, 2};
Foo a{1, 2, 3};

concept 里借助了一个万能类型 UniversalType,UniversalType 中有一个可以隐式转换成任意类的稻草人函数。然后将所有的变参 UniversalType 展开检查初始化类 T 时的参数个数是否合法。
第一个分支通过不断构造新的 UniversalType,当 concept 不满足时,说明当前参数的个数就等于类的成员数量。

  1. 首先定义了一个全局模板变量 MembersCount,默认值为0。用户可以为特定类型 T 提供特化版本以显式指定成员数量。

  2. 定义了一个名为 UniversalType 的万能类型,它可以隐式转换为任意类类型。这个类型会在计算成员数量的过程中作为一个特殊的标记,当不能再添加更多成员时停止递归。

  3. MemberCountImpl 是一个递归模板函数,用于计算类型 T 的构造函数能接受多少个参数(即视为类成员的数量)。通过 requires 语句检测能否使用给定参数 Args... 构造 T 类型的对象,并尝试添加一个 UniversalType。如果可以继续添加,则递归调用自身,增加一个 UniversalType 到参数列表中;如果不能添加,则认为找到了类成员的最大数量,返回当前的参数数量 sizeof...(Args)

  4. MemberCount 函数是对外公开的接口。首先检查用户是否已经为类型 T 特化了 MembersCount,如果有特化并且值大于0,则直接返回该特化值。否则,调用 MemberCountImpl<T>() 计算成员数量。

获取类成员类别


要想获取类的全部成员,在 cpp 17 里有个简单的方法,就是结构化绑定

Foo foo;
auto &&[a1, a2] = foo;

现在 a1 就是对 foo.n 的引用,a2 就是对 foo.str 的引用。

简单封装一下,我们需要定义一个高阶函数 VisitMembers,实现 Vistor 模式,其接受两个参数:

  1. 对象 auto&& object

  2. 一个函数 visitor,对对象全部字段进行访问、操作,签名为 void(auto &&…items) ,其中参数为变参模板

constexpr decltype(auto) VisitMembers(auto &&object, auto &&visitor) {
    using type = std::remove_cvref_t<decltype(object)>;
    constexpr auto Count = MemberCount<type>();
    ...
    if constexpr (Count == 0) {
        return visitor();
    }
    else if constexpr (Count == 1) {
        auto &&[a1] = object;
        return visitor(a1);
    }
    else if constexpr (Count == 2) {
        auto &&[a1, a2] = object;
        return visitor(a1, a2);
    }
    else if constexpr (Count == 3) {
        auto &&[a1, a2, a3] = object;
        return visitor(a1, a2, a3);
    }
    ...
}

它接受一个对象(object)及其对应的访问者(visitor)函数对象,并遍历对象的所有成员。根据 MemberCount 函数计算出的成员数量(Count)来解构对象并逐个调用访问者函数。

函数的具体实现如下:

  • 首先,移除 object 的引用和 CV 限定符,得到类型 type

  • 使用 MemberCount<type> 计算 type 类型的成员数量。

  • 根据成员数量,通过结构化绑定语法解构对象,并将成员作为参数传递给 visitor 函数。

  • visitor 的返回值被 VisitMembers 函数返回。

使用:

template<typename Type>
void func(const Type& t) {
    using T = std::remove_cvref_t<Type>;
    if constexpr(std::is_class_v<T>) {
        VisitMembers(t, [&](auto &&...items) {
            (void)((*this) << ... << items);
        });
    }
}

其中,(*this) 是一个支持类似流插入操作(如 <<)的对象,它能够处理不同类型的数据成员。

  • 35
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值