本文是<functional>系列的第4篇,
成員指標是一個非常具有C++特色的功能,更低級的語言(如C)沒有類,也就沒有成員的概念;更高級的語言(如Java)沒有指標,即使有也不會有成員指標這么拗口的東西,
上回在Stack Overflow上看到一個問題,C++是否允許delegate = object.method這種寫法,我猜他是從C#過來的,在C++中,這種寫法在語法上是不可能的,語意上可以用std::bind來實作,而本文的主題std::mem_fn,則是實作了delegate = method的功能,object插到了原來的引數串列的前面,成為新的函式物件的第一個引數,
成員指標
先說說成員指標,成員指標,分為物件成員指標與成員函式指標,下面的程式演示了如何定義和使用它們:
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);
}
定義為static的物件或函式,就好像它所在的類不存在一樣,只能用普通的指標與函式指標,
這一節的重點在于成員指標的模板匹配,首先,形如物件成員指標的型別可以匹配成員函式指標:
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可以匹配Res Class::*,其中Res為int,Class為Test,這完全符合預期,令人震驚的是,FunctionType也可以匹配Res Class::*!其中Class依然為Test,而Res為函式型別void(int),
那么是否可以寫一個類模板,只能匹配成員函式指標而無法匹配物件成員指標呢?在此之前,為了能夠更有說服力地用static_assert表示一個類沒有result_type成員型別(而不是在編譯錯誤后把代碼注釋掉),我寫了個has_result_type型別,用的是昨天剛寫過的void_t技巧:
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 { };
只匹配成員函式指標,需要加上一個可變引數:
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, "");
那么只匹配物件成員指標呢?很簡單,只需寫一個全部匹配的,再去掉成員函式指標即可:
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, "");
如果成員函式有const或&會怎樣?
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, "");
Res Class::*中的Class還是不變,但是Res變成了后加const和&的函式型別,關于這兩個型別我沒有查到相關資料,只知道它們的std::is_function_v為true,不過這就夠了,
mem_fn
懶得寫了,照搬cppreference上的代碼:
#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';
}
輸出:
Hello, world.
number: 42
data: 7
我尋思著你能讀到這兒也不用我介紹std::mem_fn了吧,我的心思在它的實作上,
順便提醒,不要跟std::mem_fun搞混,那玩意兒是C++98的化石,
實作
std::mem_fn基于std::invoke,std::invoke又基于std::result_of,所以從std::result_of講起,
SFINAE
在C++中,檢查一句陳述句是否合法有三種方式:目測、看編譯器給不給error、SFINAE,對于模板代碼,Visual Studio都智能不起來,更別說目測了;我們又不想看到編譯器的error,所以得學習SFINAE,Substitution 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;
};
__result_of_other_impl里有兩個多載函式_S_test,__result_of_impl通過decltype獲得它的回傳型別,當_Functor(_ArgTypes...)陳述句合法時,第一個_S_test安好,int優于...,多載決議為第一個,type定義為_S_test前面一長串;不合法時,第一個_S_test實體化失敗,但是模板替換失敗不是錯誤,編譯器繼續尋找正確的多載,找到第二個_S_test,它的變參模板和可變引數像黑洞一樣吞噬一切呼叫,一定能匹配上,type定義為__failure_type,
后文中凡是出現_S_test的地方都使用了SFINAE的技巧,
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和std::invoke_result本質上是相同的,無非是模板引數_Functor(_ArgTypes...)和_Functor, _ArgTypes...的區別,前者在C++17中廢棄,后者在C++17中加入,
__invoke_result借助_Functor的型別分為三種情況:
-
__result_of_impl<false, false, _Functor, _ArgTypes...>,可呼叫物件型別不是成員指標,繼承__result_of_other_impl,后者在上一節介紹過了; -
__result_of_impl<true, false, _MemPtr, _Arg>,可呼叫物件是物件成員指標,繼承__result_of_memobj:-
當
_Argval與_Class相同或_Class是_Argval的基類時(其實is_base_of就可以概括這種關系;子類成員可以呼叫基類成員指標),使用__result_of_memobj_ref,呼叫方式為.*; -
否則,呼叫引數是個指標,使用
__result_of_memobj_deref,呼叫方式為->*;
-
-
__result_of_impl<false, true, _MemPtr, _Arg, _Args...>,可呼叫物件是成員函式指標,詳細討論與上一種情況類似,不再贅述,
總之,對于合法的呼叫型別,__invoke_result最后繼承到__success_type,定義type為回傳型別;否則繼承__failure_type,沒有type成員,
Tag Dispatching
你注意到了嗎?__result_of_success把__success_type包裝了一下,加入了_Tag模板引數并定義為__invoke_type,在隨后的實體化中,__invoke_type都是以下5個型別之一:
struct __invoke_memfun_ref { };
struct __invoke_memfun_deref { };
struct __invoke_memobj_ref { };
struct __invoke_memobj_deref { };
struct __invoke_other { };
這些型別極大地簡化了__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)...);
}
__invoke中定義這個__invoke_type為__tag,然后呼叫__invoke_impl時把__tag的實體傳入,根據__tag的型別,編譯器將多載函式決議為5個__invoke_impl中對應的那個,
這種技巧稱為tag dispatching,我在std::function中也介紹過,
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回傳型別為_Mem_fn,_Mem_fn繼承_Mem_fn_base,后者分物件成員指標與成員函式指標兩種情況,operator()都轉發引數呼叫__invoke,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/36256.html
標籤:C++
