当笔者在实现一个类似函数包装器的类模板时(代码示意如下),希望能够传入一个可调用对象来构造,并自动推导出模板(C++17及以上)。但是,如何自动推导出任意一个可调用类型的返回值和参数类型,却成了一个问题。
template <class Signature>
class function {};
template <class Ret, class... Args>
class function<Ret(Args...)> {
std::function<Ret(Args...)> _function;
public:
function(const std::function<Ret(Args...) &f) : _function(f) {}
};
C++标准库中,并未提供根据函数类型获取参数类型的方法;想要获取返回值类型,可使用std::result_of
或者std::invoke_result
,但是不能仅通过函数类型来获取。因此,笔者决定实现一个function_traits
,来获取适用较为广泛的可调用类型的返回值、参数类型以及一些其他属性。
本实现需要使用C++20版本,使用了C++20中requires
的语法,以及C++20中标准库中扩充的内容。在本节的所有代码,仅做演示说明,详细代码见后面完整代码一节。
因为笔者后续对function_traits
支持了成员函数萃取,因此本小节先讲述对于可调用类型的萃取。
callable_traits
适用于对于一切可调用类型,包括函数、函数指针、仿函数、匿名函数,以及上述类型的cv限定
类型和引用限定
类型。那么第一步要做的就是对传入的类型进行一次“提纯”,先把cv限定
和引用限定
去除。采用C++20提供的std::remove_cvref
就可以去掉限定了。
template <class T>
struct callable_traits_base {};
template <class T>
struct callable_traits : callable_traits_base<std::remove_cvref_t<T>> {};
去掉限定以后,现在的类型仅剩函数、函数指针、仿函数和匿名函数了。获取函数返回值和参数类型信息的主要原理,就是对模板的偏特化。通过偏特化Ret
、Args...
组合成的类型Ret(Args...)
来识别传入的类型参数,分离出Ret
和Args...
的信息,并根据此添加一些额外的信息。函数可以直接适用于偏特化的模板了。
template <class Ret, class... Args>
struct callable_traits_base<Ret(Args...)> {
using type = Ret(Args...); // 可调用对象基本类型
using return_type = Ret; // 返回值类型
using argument_tuple = std::tuple<Args...>; // 参数包类型元组
template <size_t N>
using argument_type = std::tuple_element_t<N, argument_tuple>; // 具体参数类型
inline static constexpr size_t arity = sizeof...(Args); // 参数个数
};
但是对于仿函数和匿名函数就需要稍加处理了。我们可以提取仿函数和匿名函数重载的()
运算符的类型,来做适配。
template <class T>
struct callable_traits_base : callable_traits_base<decltype(&T::operator())> {};
但是问题出现了。对()
运算符提取类型,仍然包含了该类型的类指针(T::*)
,而且()
运算符作为成员函数,可能同样是cv限定
和引用限定
的,仍需要做进一步处理,来“提纯”到仅剩Ret(Args...)
。此处有两种做法,一种是增加偏特化,偏特化Ret(T::*)(Args...)
,Ret(T::*)(Args...) const
等等,来适配()
运算符类型;另一种是将这些类指针与限定移除,进一步“提纯”()
运算符类型。笔者最初采用第一种方法,仅做偏特化处理,在此不做展示。后续因为笔者对function_traits
做了更好地适配,使其不仅适用于可调用类型,也能够适用于成员函数类型,在这过程中实现了去掉成员函数的限定以及类指针属性,于是便采用了第二种方法。
template <class T>
struct callable_traits_base : function_traits_base<
remove_member_function_cvref_class_t<decltype(&T::operator())>> {};
上述代码中remove_member_function_cvref_class_t
的原理在下一小节中讲述。这样,仿函数和匿名函数也被解决掉了。至此可调用类型仅剩函数指针类型,同样有两种解决方案,一种是增加对于指针类型的偏特化;另一种就是使用std::remove_pointer
来移除指针,这样也不会影响没有指针的类型,但是如果是仿函数和匿名函数指针,这两个是不可调用类型,若都直接移除指针,便会被视作可调用类型而处理。因此比较简单的处理方案是,使用C++20的requires
语法,来对初始传入的内容做一个限定。
template <class T>
requires is_function_v<std::remove_pointer_t<T>> || is_functor_v<T>
struct callable_traits {};
其中的is_functor_v
依然是使用偏特化来处理。原理是如果传入类型包含重载()
运算符,将会被特化的模板识别。
template <class, class = void>
struct is_functor : false_type {};
template <class T>
struct is_functor<T, void_t<decltype(&T::operator())>> : true_type {};
至此,对于可调用类型的萃取已经实现。但是笔者在后续一系列测试时发现,对于不定参数函数,竟然无法处理。这个也很好解决,同样是通过偏特化。不过参数个数和具体参数类型仍然只算到确定参数项里面。
template <class Ret, class... Args>
struct callable_traits_base<Ret(Args..., ...)> {
using type = Ret(Args..., ...); // 可调用对象基本类型
using return_type = Ret; // 返回值类型
using argument_tuple = std::tuple<Args...>; // 参数包类型元组
template <size_t N>
using argument_type = std::tuple_element_t<N, argument_tuple>; // 具体参数类型
inline static constexpr size_t arity = sizeof...(Args); // 参数个数
};
解决完这些,可调用类型的萃取便已经全部完成。接下来笔者对于成员函数也做了一些适配。
在笔者实现成员函数包装器时,以及在实现上述可调用类型萃取时,认为处理成员函数的各种限定以及类型识别也是有必要的,因此实现了成员函数萃取。核心思想仍然是通过偏特化。
template <class Ret, class Class, class... Args>
struct member_function_traits<Ret (Class::*)(Args...)> {
using type = Ret (Class::*)(Args...);
using class_type = Class;
using return_type = Ret;
using argument_tuple = std::tuple<Args...>;
template <size_t N>
using argument_type = std::tuple_element_t<N, argument_tuple>;
inline static constexpr size_t arity = sizeof...(Args);
};
对于成员函数,并没有像是std::remove_cvref
一样可以直接去除成员函数限定的工具,因此就对所有情况做了偏特化。情况较多,因此采用宏来生成。
template<class T> requires is_member_function_pointer_v<remove_cvref_t<T>>
struct member_function_traits : member_function_traits<remove_cvref_t<T>> {};
#define _MEMBER_FUNCTION_TRAITS(cv, ref, ...) \
template <class R, class C, class... Args> \
struct member_function_traits<R (C::*)(Args... __VA_ARGS__) cv ref> { \
using type = R (C::*)(Args... __VA_ARGS__) cv ref; \
using class_type = cv C ref; \
using return_type = R; \
using argument_tuple = tuple<Args...>; \
template <size_t N> \
using argument_type = tuple_element_t<N, argument_tuple>; \
inline static constexpr size_t arity = sizeof...(Args); \
};
#define __MEMBER_FUNCTION_TRAITS(cv) \
_MEMBER_FUNCTION_TRAITS(cv, ) \
_MEMBER_FUNCTION_TRAITS(cv, , , ...) \
_MEMBER_FUNCTION_TRAITS(cv, &) \
_MEMBER_FUNCTION_TRAITS(cv, &, , ...) \
_MEMBER_FUNCTION_TRAITS(cv, &&) \
_MEMBER_FUNCTION_TRAITS(cv, &&, , ...)
__MEMBER_FUNCTION_TRAITS()
__MEMBER_FUNCTION_TRAITS(const)
__MEMBER_FUNCTION_TRAITS(volatile)
__MEMBER_FUNCTION_TRAITS(const volatile)
至此,对于成员函数的萃取也已经完成。笔者也实现了一些其他的对于成员函数类型操作的类,比如去除成员函数的限定、去除成员函数的类指针、判断成员函数是否为某种限定等等各种各样的操作,在此不详细展开。具体原理均使用了这个member_function_traits
,以及模板偏特化,详细代码请见后面的完整代码一节。
完成了成员函数萃取和可调用类型萃取,我们可以将这两个合二为一,用function_traits
同时支持这二者。使用一个模板参数为判断,特化这个布尔值模板参数来选择性继承callable_traits
和member_function_traits
。
template<class T, bool Is = is_callable_v<T>>
requires is_callable_v<T> || is_member_function_pointer_v<remove_cvref_t<T>>
struct function_traits : callable_traits<T> {};
template<class T>
struct function_traits<T, false> : member_function_traits<T> {};
完整代码请见https://github.com/Fabulousxu/XH-CppTools仓库里的function_traits.h。
// C++20 function_traits.h
// Author: [email protected]
// Last Updated: 2024-08-19
// This header file defines a series of function type traits, expanding on the
// type traits not fully provided by the standard library for function types.
// It includes utilities for checking callable types, such as function pointers
// and functors, and for checking and removing qualifiers of member functions.
// Most mainly, it offers function traits for callable types and member
// functions, allowing users to get function return types, argument types, and
// the class type of member functions. This function traits supports variadic
// functions and recognizes and erases cv-qualifiers and reference-qualifiers
// from any callable types and member functions.
#ifndef _XH_FUNCTION_TRAITS_H_
#define _XH_FUNCTION_TRAITS_H_
#include
#include
using namespace std;
namespace xh {
/************************
* check if is callable *
************************/
template<class T>
struct is_function_pointer
: bool_constant<is_pointer_v<T> && is_function_v<remove_pointer_t<T>>> {};
template<class T>
struct is_function_or_pointer : is_function<remove_pointer_t<T>> {};
template<class, class = void>
struct is_functor : false_type {};
template<class T>
struct is_functor<T, void_t<decltype(&T::operator())>> : true_type {};
template<class T>
struct is_function_or_pointer_or_functor
: bool_constant<is_function_or_pointer<T>::value || is_functor<T>::value> {};
template<class T>
struct is_callable : is_function_or_pointer_or_functor<remove_cvref_t<T>> {};
// _t aliases and _v variables
template<class T>
inline constexpr bool is_function_pointer_v = is_function_pointer<T>::value;
template<class T>
inline constexpr bool is_function_or_pointer_v =
is_function_or_pointer<T>::value;
template<class T>
inline constexpr bool is_functor_v = is_functor<T>::value;
template<class T>
inline constexpr bool is_function_or_pointer_or_functor_v =
is_function_or_pointer_or_functor<T>::value;
template<class T>
inline constexpr bool is_callable_v = is_callable<T>::value;
/**************************
* member function traits *
**************************/
template<class T> requires is_member_function_pointer_v<remove_cvref_t<T>>
struct member_function_traits : member_function_traits<remove_cvref_t<T>> {};
#define _MEMBER_FUNCTION_TRAITS(cv, ref, ...) \
template <class R, class C, class... Args> \
struct member_function_traits<R (C::*)(Args... __VA_ARGS__) cv ref> { \
using type = R (C::*)(Args... __VA_ARGS__) cv ref; \
using class_type = cv C ref; \
using return_type = R; \
using argument_tuple = tuple<Args...>; \
template <size_t N> \
using argument_type = tuple_element_t<N, argument_tuple>; \
inline static constexpr size_t arity = sizeof...(Args); \
};
#define __MEMBER_FUNCTION_TRAITS(cv) \
_MEMBER_FUNCTION_TRAITS(cv, ) \
_MEMBER_FUNCTION_TRAITS(cv, , , ...) \
_MEMBER_FUNCTION_TRAITS(cv, &) \
_MEMBER_FUNCTION_TRAITS(cv, &, , ...) \
_MEMBER_FUNCTION_TRAITS(cv, &&) \
_MEMBER_FUNCTION_TRAITS(cv, &&, , ...)
__MEMBER_FUNCTION_TRAITS()
__MEMBER_FUNCTION_TRAITS(const)
__MEMBER_FUNCTION_TRAITS(volatile)
__MEMBER_FUNCTION_TRAITS(const volatile)
#undef _MEMBER_FUNCTION_TRAITS
#undef __MEMBER_FUNCTION_TRAITS
// _t aliases and _v variables
template<class T>
using member_function_traits_t = member_function_traits<T>::type;
template<class T>
using member_function_class_t = member_function_traits<T>::class_type;
template<class T>
using member_function_return_t = member_function_traits<T>::return_type;
template<class T>
using member_function_argument_tuple =
member_function_traits<T>::argument_tuple;
template<class T, size_t N>
using member_function_argument_t =
member_function_traits<T>::template argument_type<N>;
template<class T>
inline constexpr size_t member_function_arity_v =
member_function_traits<T>::arity;
/******************************************
* check if member function has qualifier *
******************************************/
template<class T> requires is_member_function_pointer_v<T>
struct is_member_function_const : is_const<member_function_class_t<T>> {};
template<class T> requires is_member_function_pointer_v<T>
struct is_member_function_volatile : is_volatile<member_function_class_t<T>> {};
template<class T> requires is_member_function_pointer_v<T>
struct is_member_function_reference
: is_reference<member_function_class_t<T>> {};
template<class T> requires is_member_function_pointer_v<T>
struct is_member_function_lvalue_reference
: is_lvalue_reference<member_function_class_t<T>> {};
template<class T> requires is_member_function_pointer_v<T>
struct is_member_function_rvalue_reference
: is_rvalue_reference<member_function_class_t<T>> {};
// _t aliases and _v variables
template<class T>
inline constexpr bool is_member_function_const_v =
is_member_function_const<T>::value;
template<class T>
inline constexpr bool is_member_function_volatile_v =
is_member_function_volatile<T>::value;
template<class T>
inline constexpr bool is_member_function_reference_v =
is_member_function_reference<T>::value;
template<class T>
inline constexpr bool is_member_function_lvalue_reference_v =
is_member_function_lvalue_reference<T>::value;
template<class T>
inline constexpr bool is_member_function_rvalue_reference_v =
is_member_function_rvalue_reference<T>::value;
/************************************************
* remove qualifier or class of member function *
************************************************/
template<class T> requires is_member_function_pointer_v<T>
struct remove_member_function_reference {};
template<class T> requires is_member_function_pointer_v<T>
struct remove_member_function_const {};
template<class T> requires is_member_function_pointer_v<T>
struct remove_member_function_volatile {};
template<class T> requires is_member_function_pointer_v<T>
struct remove_member_function_cv {};
template<class T> requires is_member_function_pointer_v<T>
struct remove_member_function_cvref {};
#define _REMOVE_MEMBER_FUNCTION(name, cvref, result) \
template <class R, class C, class... Args> \
struct remove_member_function_##name<R (C::*)(Args...) cvref> \
{ using type = R (C::*)(Args...) result; }; \
template <class R, class C, class... Args> \
struct remove_member_function_##name<R (C::*)(Args..., ...) cvref> \
{ using type = R (C::*)(Args..., ...) result; };
#define __REMOVE_MEMBER_FUNCTION(name, _, _lref, _rref, _const, _const_lref, \
_const_rref, _volatile, _volatile_lref, \
_volatile_rref, _cv, _cv_lref, _cv_rref) \
_REMOVE_MEMBER_FUNCTION(name, , _) \
_REMOVE_MEMBER_FUNCTION(name, &, _lref) \
_REMOVE_MEMBER_FUNCTION(name, &&, _rref) \
_REMOVE_MEMBER_FUNCTION(name, const, _const) \
_REMOVE_MEMBER_FUNCTION(name, const &, _const_lref) \
_REMOVE_MEMBER_FUNCTION(name, const &&, _const_rref) \
_REMOVE_MEMBER_FUNCTION(name, volatile, _volatile) \
_REMOVE_MEMBER_FUNCTION(name, volatile &, _volatile_lref) \
_REMOVE_MEMBER_FUNCTION(name, volatile &&, _volatile_rref) \
_REMOVE_MEMBER_FUNCTION(name, const volatile, _cv) \
_REMOVE_MEMBER_FUNCTION(name, const volatile &, _cv_lref) \
_REMOVE_MEMBER_FUNCTION(name, const volatile &&, _cv_rref)
__REMOVE_MEMBER_FUNCTION(reference, , , , const, const, const, volatile,
volatile, volatile, const volatile, const volatile, const volatile)
__REMOVE_MEMBER_FUNCTION(const, , &, &&, , const &, const &&, volatile,
volatile &, volatile &&, volatile, const volatile &, const volatile &&)
__REMOVE_MEMBER_FUNCTION(volatile, , &, &&, const, const &, const &&, ,
volatile &, volatile &&, const, const volatile &, const volatile &&)
__REMOVE_MEMBER_FUNCTION(cv, , &, &&, , const &, const &&, , volatile &,
volatile &&, , const volatile &, const volatile &&)
__REMOVE_MEMBER_FUNCTION(cvref, , , , , , , , , , , ,)
#undef _REMOVE_MEMBER_FUNCTION
#undef __REMOVE_MEMBER_FUNCTION
template<class T> requires is_member_function_pointer_v<T>
struct remove_member_function_class {};
template<class T, class C>
struct remove_member_function_class<T C::*> { using type = T; };
template<class T> requires is_member_function_pointer_v<T>
struct remove_member_function_cvref_class : remove_member_function_class<
typename remove_member_function_cvref<T>::type> {};
// _t aliases and _v variables
template<class T>
using remove_member_function_reference_t =
remove_member_function_reference<T>::type;
template<class T>
using remove_member_function_const_t = remove_member_function_const<T>::type;
template<class T>
using remove_member_function_volatile_t =
remove_member_function_volatile<T>::type;
template<class T>
using remove_member_function_cv_t = remove_member_function_cv<T>::type;
template<class T>
using remove_member_function_cvref_t = remove_member_function_cvref<T>::type;
template<class T>
using remove_member_function_class_t = remove_member_function_class<T>::type;
template<class T>
using remove_member_function_cvref_class_t =
remove_member_function_cvref_class<T>::type;
/*******************
* callable traits *
*******************/
template<class T> requires is_function_or_pointer_or_functor_v<T>
struct function_or_pointer_or_functor_traits
: function_or_pointer_or_functor_traits<
remove_member_function_cvref_class_t<decltype(&T::operator())>> {};
#define _FUNCTION_OR_POINTER_OR_FUNCTOR_TRAITS(...) \
template <class R, class... Args> \
struct function_or_pointer_or_functor_traits<R(Args... __VA_ARGS__)> { \
using type = R(Args... __VA_ARGS__); \
using return_type = R; \
using argument_tuple = tuple<Args...>; \
template <size_t N> \
using argument_type = tuple_element_t<N, argument_tuple>; \
inline static constexpr size_t arity = sizeof...(Args); \
};
_FUNCTION_OR_POINTER_OR_FUNCTOR_TRAITS()
_FUNCTION_OR_POINTER_OR_FUNCTOR_TRAITS(, ...)
#undef _FUNCTION_OR_POINTER_OR_FUNCTOR_TRAITS
template<class T> requires is_callable_v<T>
struct callable_traits : function_or_pointer_or_functor_traits<
remove_pointer_t<remove_cvref_t<T>>> {};
// _t aliases and _v variables
template<class T>
using callable_traits_t = callable_traits<T>::type;
template<class T>
using callable_return_t = callable_traits<T>::return_type;
template<class T>
using callable_argument_tuple = callable_traits<T>::argument_tuple;
template<class T, size_t N>
using callable_argument_t = callable_traits<T>::template argument_type<N>;
template<class T>
inline constexpr size_t callable_arity_v = callable_traits<T>::arity;
/*******************
* function traits *
*******************/
template<class T, bool Is = is_callable_v<T>>
requires is_callable_v<T> || is_member_function_pointer_v<remove_cvref_t<T>>
struct function_traits : callable_traits<T> {};
template<class T>
struct function_traits<T, false> : member_function_traits<T> {};
// _t aliases and _v variables
template<class T>
using function_traits_t = function_traits<T>::type;
template<class T>
using function_class_t = function_traits<T>::class_type;
template<class T>
using function_return_t = function_traits<T>::return_type;
template<class T>
using function_argument_tuple = function_traits<T>::argument_tuple;
template<class T, size_t N>
using function_argument_t = function_traits<T>::template argument_type<N>;
template<class T>
inline constexpr size_t function_arity_v = function_traits<T>::arity;
}; // namespace xh
#endif //_XH_FUNCTION_TRAITS_H_
回到最初的那个问题,传入可调用对象构造,自动推导出模板类型便已经可以实现。
template <class T>
function(T) -> function<function_traits<T>>;
感谢支持!