C++玩转模板之——函数萃取function traits

目录

  • 前言
  • 一、实现原理
    • (一)可调用类型萃取
    • (二)成员函数萃取
  • 二、完整代码
  • 总结


前言

当笔者在实现一个类似函数包装器的类模板时(代码示意如下),希望能够传入一个可调用对象来构造,并自动推导出模板(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>> {};

去掉限定以后,现在的类型仅剩函数、函数指针、仿函数和匿名函数了。获取函数返回值和参数类型信息的主要原理,就是对模板的偏特化。通过偏特化RetArgs...组合成的类型Ret(Args...)来识别传入的类型参数,分离出RetArgs...的信息,并根据此添加一些额外的信息。函数可以直接适用于偏特化的模板了。

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_traitsmember_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>>;

感谢支持!

你可能感兴趣的:(c++)