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
是可变参数模板,表示零个或多个参数类型。
-
特化版中定义了以下成员:
-
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
为指向该类型函数的指针。
-
-
template<size_t I> struct args
:定义一个内部模板类args
,用于获取第I
个参数的类型。通过static_assert
确保索引有效,并使用std::tuple_element
和std::tuple<Args...>
获取对应参数类型。 -
using tuple_type = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...>;
:创建一个元组(tuple),包含所有参数类型(去除引用和cv限定符)。 -
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(如 const
、volatile
、&
、&&
等)。该特化表明成员函数的 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
类型和函数指针类型。
-
to_function
函数模板:- 接受一个
const
引用或右值引用Function
类型的参数lambda
。 - 利用
function_traits
模板类获取Function
类型的stl_function_type
,这是一个与Function
兼容的std::function
类型。 - 使用
static_cast
将lambda
转换为stl_function_type
类型并返回。
- 接受一个
-
to_function_pointer
函数模板:- 接受一个
const
引用的Function
类型参数lambda
。 - 利用
function_traits
模板类获取Function
类型的pointer
类型,这是一个指向与Function
符合相同签名的函数指针类型。 - 使用
static_cast
将lambda
转换为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 不满足时,说明当前参数的个数就等于类的成员数量。
-
首先定义了一个全局模板变量
MembersCount
,默认值为0。用户可以为特定类型T
提供特化版本以显式指定成员数量。 -
定义了一个名为
UniversalType
的万能类型,它可以隐式转换为任意类类型。这个类型会在计算成员数量的过程中作为一个特殊的标记,当不能再添加更多成员时停止递归。 -
MemberCountImpl
是一个递归模板函数,用于计算类型T
的构造函数能接受多少个参数(即视为类成员的数量)。通过requires
语句检测能否使用给定参数Args...
构造T
类型的对象,并尝试添加一个UniversalType
。如果可以继续添加,则递归调用自身,增加一个UniversalType
到参数列表中;如果不能添加,则认为找到了类成员的最大数量,返回当前的参数数量sizeof...(Args)
。 -
MemberCount
函数是对外公开的接口。首先检查用户是否已经为类型T
特化了MembersCount
,如果有特化并且值大于0,则直接返回该特化值。否则,调用MemberCountImpl<T>()
计算成员数量。
获取类成员类别
要想获取类的全部成员,在 cpp 17 里有个简单的方法,就是结构化绑定
Foo foo;
auto &&[a1, a2] = foo;
现在 a1 就是对 foo.n 的引用,a2 就是对 foo.str 的引用。
简单封装一下,我们需要定义一个高阶函数 VisitMembers,实现 Vistor 模式,其接受两个参数:
-
对象 auto&& object
-
一个函数 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) 是一个支持类似流插入操作(如 <<)的对象,它能够处理不同类型的数据成员。