This article is <function>series Article 4.
The member pointer is a very C++ feature.Lower-level languages (such as C) have no classes and no concept of members; higher-level languages (such as Java) have no pointers, and even if there are, there won't be anything as awkward as member pointers.
Last time you saw a question on Stack Overflow whether C++ allows delegate = object.method.I guess he came from C#.In C++, this writing is syntactically impossible and semantically possible with std::bind.The theme std::mem_fn of this article implements the function of delegate = method, and object is inserted in front of the original parameter list, becoming the first parameter of the new function object.
member pointer
Let's start with member pointers.Member pointer, divided into object member pointer and member function pointer.The following program demonstrates how to define and use them:
struct Test { int object; void function(int) { } }; int main() { Test test; Test* ptr = &test; int Test::* po = &Test::object; test.*po; ptr->*po; void (Test::*pf)(int) = &Test::function; (test.*pf)(0); (ptr->*pf)(0); }
An object or function defined as a static can only be used with ordinary pointers and function pointers, just as its class does not exist.
This section focuses on template matching of member pointers.First, the type of object member pointer matches the member function pointer:
template<typename> struct member_test; template<typename Res, typename Class> struct member_test<Res Class::*> { using result_type = Res; using class_type = Class; }; struct Test { int object; void function(int) { } }; using ObjectType = decltype(&Test::object); using FunctionType = decltype(&Test::function); static_assert(std::is_same< typename member_test<ObjectType>::result_type, int>::value, ""); static_assert(std::is_same< typename member_test<ObjectType>::class_type, Test>::value, ""); static_assert(std::is_same< typename member_test<FunctionType>::result_type, void(int)>::value, ""); static_assert(std::is_same< typename member_test<FunctionType>::class_type, Test>::value, "");
ObjectType can match Res Class:::*, where Res is int and Class is Test, which is exactly what you expect.Surprisingly, FunctionType s can also match Res Class::*!Where Class is still Test and Res is the function type void(int).
So can you write a class template that only matches member function pointers but not object member pointers?Previously, to make it more persuasive to use static_assert to indicate that a class does not have a result_type member type (instead of commenting out the code after compilation errors), I wrote a has_result_type type, which I just wrote yesterday void_t technique:
template<typename T, typename = void> struct has_result_type : std::false_type { }; template<typename T> struct has_result_type<T, std::void_t<typename T::result_type>> : std::true_type { };
Matches only member function pointers, requiring a variable parameter:
template<typename> struct member_function_test; template<typename Res, typename Class, typename... Args> struct member_function_test<Res (Class::*)(Args...)> { using result_type = Res; using class_type = Class; }; static_assert(!has_result_type< member_function_test<ObjectType>>::value, ""); static_assert(has_result_type< member_function_test<FunctionType>>::value, ""); static_assert(std::is_same< typename member_function_test<FunctionType>::result_type, void>::value, "");
What about matching only object member pointers?Simply, just write a match and remove the member function pointer:
template<typename> struct member_object_test; template<typename Res, typename Class> struct member_object_test<Res Class::*> { using result_type = Res; using class_type = Class; }; template<typename Res, typename Class, typename... Args> struct member_object_test<Res (Class::*)(Args...)> { }; static_assert(has_result_type< member_object_test<ObjectType>>::value, ""); static_assert(!has_result_type< member_object_test<FunctionType>>::value, ""); static_assert(std::is_same< typename member_object_test<ObjectType>::result_type, int>::value, "");
What happens if a member function has const or &?
struct Test { int object; void function(int) { } void function_const(int) const { } void function_ref(int) & { } }; static_assert(std::is_same< typename member_test<decltype(&Test::function_const)>::result_type, void(int) const>::value, ""); static_assert(std::is_same< typename member_test<decltype(&Test::function_const)>::class_type, Test>::value, ""); static_assert(std::is_same< typename member_test<decltype(&Test::function_ref)>::result_type, void(int) &>::value, ""); static_assert(std::is_same< typename member_test<decltype(&Test::function_ref)>::class_type, Test>::value, "");
The Class in Res Class:::* remains unchanged, but Res becomes the function type followed by const and &.I don't find any information about these two types except that their std::is_function_v is true.But that's enough.
mem_fn
Lazy to write, copy cppreference Code on:
#include <functional> #include <iostream> struct Foo { void display_greeting() { std::cout << "Hello, world.\n"; } void display_number(int i) { std::cout << "number: " << i << '\n'; } int data = 7; }; int main() { Foo f; auto greet = std::mem_fn(&Foo::display_greeting); greet(f); auto print_num = std::mem_fn(&Foo::display_number); print_num(f, 42); auto access_data = std::mem_fn(&Foo::data); std::cout << "data: " << access_data(f) << '\n'; }
Output:
Hello, world. number: 42 data: 7
I thought you could read this without introducing std::mem_fn. My mind is on its implementation.
By the way, don't confuse std::mem_fun, it's a C++98 fossil.
Realization
std::mem_fn is based on std::invoke, std::invoke is also based on std::result_of, so start with std::result_of.
SFINAE
In C++, there are three ways to check the legality of a sentence: to visualize, to see if the compiler gives no error, to see SFINAE.For template code, Visual Studio is not smart, let alone visual; we don't want to see compiler errors, so we have to learn SFINAE, Substitution Failure Is Not An Error. Replacement failure is not an error.
struct __result_of_other_impl { template<typename _Fn, typename... _Args> static __result_of_success<decltype( std::declval<_Fn>()(std::declval<_Args>()...) ), __invoke_other> _S_test(int); template<typename...> static __failure_type _S_test(...); }; template<typename _Functor, typename... _ArgTypes> struct __result_of_impl<false, false, _Functor, _ArgTypes...> : private __result_of_other_impl { typedef decltype(_S_test<_Functor, _ArgTypes...>(0)) type; };
There are two overloaded functions _S_test in u result_of_other_impl, and u result_of_impl obtains its return type through decltype.When the _Functor(_ArgTypes...) statement is valid, the first _S_test is OK, int is better than..., the overload resolution is the first, and the type is defined as a long string before _S_test; when it is not valid, the first _S_test instantiation fails, but the template replacement fails, and the compiler continues to look for the correct overload and finds the second _S_test, whose variable parameter templates and variable parameters are like black holesDevokes all calls, must match, type is defined as u failure_type`.
SFINAE techniques are used wherever _S_test appears later.
result_of
// For several sfinae-friendly trait implementations we transport both the // result information (as the member type) and the failure information (no // member type). This is very similar to std::enable_if, but we cannot use // them, because we need to derive from them as an implementation detail. template<typename _Tp> struct __success_type { typedef _Tp type; }; struct __failure_type { }; /// result_of template<typename _Signature> class result_of; // Sfinae-friendly result_of implementation: #define __cpp_lib_result_of_sfinae 201210 struct __invoke_memfun_ref { }; struct __invoke_memfun_deref { }; struct __invoke_memobj_ref { }; struct __invoke_memobj_deref { }; struct __invoke_other { }; // Associate a tag type with a specialization of __success_type. template<typename _Tp, typename _Tag> struct __result_of_success : __success_type<_Tp> { using __invoke_type = _Tag; }; // [func.require] paragraph 1 bullet 1: struct __result_of_memfun_ref_impl { template<typename _Fp, typename _Tp1, typename... _Args> static __result_of_success<decltype( (std::declval<_Tp1>().*std::declval<_Fp>())(std::declval<_Args>()...) ), __invoke_memfun_ref> _S_test(int); template<typename...> static __failure_type _S_test(...); }; template<typename _MemPtr, typename _Arg, typename... _Args> struct __result_of_memfun_ref : private __result_of_memfun_ref_impl { typedef decltype(_S_test<_MemPtr, _Arg, _Args...>(0)) type; }; // [func.require] paragraph 1 bullet 2: struct __result_of_memfun_deref_impl { template<typename _Fp, typename _Tp1, typename... _Args> static __result_of_success<decltype( ((*std::declval<_Tp1>()).*std::declval<_Fp>())(std::declval<_Args>()...) ), __invoke_memfun_deref> _S_test(int); template<typename...> static __failure_type _S_test(...); }; template<typename _MemPtr, typename _Arg, typename... _Args> struct __result_of_memfun_deref : private __result_of_memfun_deref_impl { typedef decltype(_S_test<_MemPtr, _Arg, _Args...>(0)) type; }; // [func.require] paragraph 1 bullet 3: struct __result_of_memobj_ref_impl { template<typename _Fp, typename _Tp1> static __result_of_success<decltype( std::declval<_Tp1>().*std::declval<_Fp>() ), __invoke_memobj_ref> _S_test(int); template<typename, typename> static __failure_type _S_test(...); }; template<typename _MemPtr, typename _Arg> struct __result_of_memobj_ref : private __result_of_memobj_ref_impl { typedef decltype(_S_test<_MemPtr, _Arg>(0)) type; }; // [func.require] paragraph 1 bullet 4: struct __result_of_memobj_deref_impl { template<typename _Fp, typename _Tp1> static __result_of_success<decltype( (*std::declval<_Tp1>()).*std::declval<_Fp>() ), __invoke_memobj_deref> _S_test(int); template<typename, typename> static __failure_type _S_test(...); }; template<typename _MemPtr, typename _Arg> struct __result_of_memobj_deref : private __result_of_memobj_deref_impl { typedef decltype(_S_test<_MemPtr, _Arg>(0)) type; }; template<typename _MemPtr, typename _Arg> struct __result_of_memobj; template<typename _Res, typename _Class, typename _Arg> struct __result_of_memobj<_Res _Class::*, _Arg> { typedef typename remove_cv<typename remove_reference< _Arg>::type>::type _Argval; typedef _Res _Class::* _MemPtr; typedef typename conditional<__or_<is_same<_Argval, _Class>, is_base_of<_Class, _Argval>>::value, __result_of_memobj_ref<_MemPtr, _Arg>, __result_of_memobj_deref<_MemPtr, _Arg> >::type::type type; }; template<typename _MemPtr, typename _Arg, typename... _Args> struct __result_of_memfun; template<typename _Res, typename _Class, typename _Arg, typename... _Args> struct __result_of_memfun<_Res _Class::*, _Arg, _Args...> { typedef typename remove_cv<typename remove_reference< _Arg>::type>::type _Argval; typedef _Res _Class::* _MemPtr; typedef typename conditional<__or_<is_same<_Argval, _Class>, is_base_of<_Class, _Argval>>::value, __result_of_memfun_ref<_MemPtr, _Arg, _Args...>, __result_of_memfun_deref<_MemPtr, _Arg, _Args...> >::type::type type; }; // _GLIBCXX_RESOLVE_LIB_DEFECTS // 2219. INVOKE-ing a pointer to member with a reference_wrapper // as the object expression // Used by result_of, invoke etc. to unwrap a reference_wrapper. template<typename _Tp, typename _Up = typename decay<_Tp>::type> struct __inv_unwrap { using type = _Tp; }; template<typename _Tp, typename _Up> struct __inv_unwrap<_Tp, reference_wrapper<_Up>> { using type = _Up&; }; template<bool, bool, typename _Functor, typename... _ArgTypes> struct __result_of_impl { typedef __failure_type type; }; template<typename _MemPtr, typename _Arg> struct __result_of_impl<true, false, _MemPtr, _Arg> : public __result_of_memobj<typename decay<_MemPtr>::type, typename __inv_unwrap<_Arg>::type> { }; template<typename _MemPtr, typename _Arg, typename... _Args> struct __result_of_impl<false, true, _MemPtr, _Arg, _Args...> : public __result_of_memfun<typename decay<_MemPtr>::type, typename __inv_unwrap<_Arg>::type, _Args...> { }; // [func.require] paragraph 1 bullet 5: struct __result_of_other_impl { template<typename _Fn, typename... _Args> static __result_of_success<decltype( std::declval<_Fn>()(std::declval<_Args>()...) ), __invoke_other> _S_test(int); template<typename...> static __failure_type _S_test(...); }; template<typename _Functor, typename... _ArgTypes> struct __result_of_impl<false, false, _Functor, _ArgTypes...> : private __result_of_other_impl { typedef decltype(_S_test<_Functor, _ArgTypes...>(0)) type; }; // __invoke_result (std::invoke_result for C++11) template<typename _Functor, typename... _ArgTypes> struct __invoke_result : public __result_of_impl< is_member_object_pointer< typename remove_reference<_Functor>::type >::value, is_member_function_pointer< typename remove_reference<_Functor>::type >::value, _Functor, _ArgTypes... >::type { }; template<typename _Functor, typename... _ArgTypes> struct result_of<_Functor(_ArgTypes...)> : public __invoke_result<_Functor, _ArgTypes...> { }; /// std::invoke_result template<typename _Functor, typename... _ArgTypes> struct invoke_result : public __invoke_result<_Functor, _ArgTypes...> { };
std::result_of is essentially the same as std::invoke_result, except for the difference between the template parameters _Functor(_ArgTypes...) and _Functor, _ArgTypes..., which were discarded in C++17 and added in C++17.
With the help of the type of _Functor, u invoke_result is divided into three cases:
-
_u result_of_impl<false, false, _Functor, _ArgTypes...>, the callable object type is not a member pointer, inherits u result_of_other_impl, which is described in the previous section;
-
_u result_of_impl<true, false, _MemPtr, _Arg>, callable object is object member pointer, inherits u result_of_memobj:
-
When _Argval is the same as _Class or when _Class is the base class of _Argval (this relationship can be generalized by is_base_of; members of a subclass can call pointers to members of the base class, using u result_of_memobj_ref, in the form of. *);
-
Otherwise, the call parameter is a pointer, using u result_of_memobj_deref, and the call method is ->*;
-
-
_u result_of_impl<false, true, _MemPtr, _Arg, _Args...>, the callable object is a member function pointer, which is discussed in detail similar to the previous case and will not be repeated.
In summary, for legal call types, u invoke_result finally inherits to u success_type, defining type as return type; otherwise, it inherits u failure_type and has no type member.
Tag Dispatching
Did you notice?_u result_of_success wraps u success_type, adds the _Tag template parameter, and defines it as u invoke_type.In subsequent instantiations, u invoke_type is one of the following five types:
struct __invoke_memfun_ref { }; struct __invoke_memfun_deref { }; struct __invoke_memobj_ref { }; struct __invoke_memobj_deref { }; struct __invoke_other { };
These types greatly simplify the implementation of u invoke:
// Used by __invoke_impl instead of std::forward<_Tp> so that a // reference_wrapper is converted to an lvalue-reference. template<typename _Tp, typename _Up = typename __inv_unwrap<_Tp>::type> constexpr _Up&& __invfwd(typename remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Up&&>(__t); } template<typename _Res, typename _Fn, typename... _Args> constexpr _Res __invoke_impl(__invoke_other, _Fn&& __f, _Args&&... __args) { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); } template<typename _Res, typename _MemFun, typename _Tp, typename... _Args> constexpr _Res __invoke_impl(__invoke_memfun_ref, _MemFun&& __f, _Tp&& __t, _Args&&... __args) { return (__invfwd<_Tp>(__t).*__f)(std::forward<_Args>(__args)...); } template<typename _Res, typename _MemFun, typename _Tp, typename... _Args> constexpr _Res __invoke_impl(__invoke_memfun_deref, _MemFun&& __f, _Tp&& __t, _Args&&... __args) { return ((*std::forward<_Tp>(__t)).*__f)(std::forward<_Args>(__args)...); } template<typename _Res, typename _MemPtr, typename _Tp> constexpr _Res __invoke_impl(__invoke_memobj_ref, _MemPtr&& __f, _Tp&& __t) { return __invfwd<_Tp>(__t).*__f; } template<typename _Res, typename _MemPtr, typename _Tp> constexpr _Res __invoke_impl(__invoke_memobj_deref, _MemPtr&& __f, _Tp&& __t) { return (*std::forward<_Tp>(__t)).*__f; } /// Invoke a callable object. template<typename _Callable, typename... _Args> constexpr typename __invoke_result<_Callable, _Args...>::type __invoke(_Callable&& __fn, _Args&&... __args) noexcept(__is_nothrow_invocable<_Callable, _Args...>::value) { using __result = __invoke_result<_Callable, _Args...>; using __type = typename __result::type; using __tag = typename __result::__invoke_type; return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn), std::forward<_Args>(__args)...); } /// Invoke a callable object. template<typename _Callable, typename... _Args> inline invoke_result_t<_Callable, _Args...> invoke(_Callable&& __fn, _Args&&... __args) noexcept(is_nothrow_invocable_v<_Callable, _Args...>) { return std::__invoke(std::forward<_Callable>(__fn), std::forward<_Args>(__args)...); }
This u invoke_type is defined as u tag in u invoke, then an instance of u tag is passed in when u invoke_impl is called. Depending on the type of u tag, the compiler resolves the overloaded function to the corresponding one in the five u invoke_impls.
This technique is called tag dispatching and I am std::function It is also described in.
mem_fn
template<typename _MemFunPtr, bool __is_mem_fn = is_member_function_pointer<_MemFunPtr>::value> class _Mem_fn_base : public _Mem_fn_traits<_MemFunPtr>::__maybe_type { using _Traits = _Mem_fn_traits<_MemFunPtr>; using _Arity = typename _Traits::__arity; using _Varargs = typename _Traits::__vararg; template<typename _Func, typename... _BoundArgs> friend struct _Bind_check_arity; _MemFunPtr _M_pmf; public: using result_type = typename _Traits::__result_type; explicit constexpr _Mem_fn_base(_MemFunPtr __pmf) noexcept : _M_pmf(__pmf) { } template<typename... _Args> auto operator()(_Args&&... __args) const noexcept(noexcept( std::__invoke(_M_pmf, std::forward<_Args>(__args)...))) -> decltype(std::__invoke(_M_pmf, std::forward<_Args>(__args)...)) { return std::__invoke(_M_pmf, std::forward<_Args>(__args)...); } }; template<typename _MemObjPtr> class _Mem_fn_base<_MemObjPtr, false> { using _Arity = integral_constant<size_t, 0>; using _Varargs = false_type; template<typename _Func, typename... _BoundArgs> friend struct _Bind_check_arity; _MemObjPtr _M_pm; public: explicit constexpr _Mem_fn_base(_MemObjPtr __pm) noexcept : _M_pm(__pm) { } template<typename _Tp> auto operator()(_Tp&& __obj) const noexcept(noexcept(std::__invoke(_M_pm, std::forward<_Tp>(__obj)))) -> decltype(std::__invoke(_M_pm, std::forward<_Tp>(__obj))) { return std::__invoke(_M_pm, std::forward<_Tp>(__obj)); } }; template<typename _MemberPointer> struct _Mem_fn; // undefined template<typename _Res, typename _Class> struct _Mem_fn<_Res _Class::*> : _Mem_fn_base<_Res _Class::*> { using _Mem_fn_base<_Res _Class::*>::_Mem_fn_base; }; template<typename _Tp, typename _Class> inline _Mem_fn<_Tp _Class::*> mem_fn(_Tp _Class::* __pm) noexcept { return _Mem_fn<_Tp _Class::*>(__pm); }
std::mem_fn returns type _Mem_fn, _Mem_fn inherits _Mem_fn_base, which is divided into object member pointer and member function pointer. operator() forwards parameter call u invoke.