C++ 实现 std::move_only_function
0.Intro
std::move_only_function
是 C++23 引入的一个只支持移动语义的函数包装器。
与 std::function
相似,std::move_only_function
也提供可调用目标的泛型包装。
但这两个类型支持的值语义不同:std::move_only_function
只能像使用 std::unique_ptr
一样,可移动、可交换但不可复制;std::function
则允许复制、交换和移动。
#include <functional>
#include <iostream>
#include <memory>int main()
{std::move_only_function<void()> fn;auto ptr = std::make_unique<int>( 114 );fn = [res = std::move( ptr )]() { std::cout << *res << std::endl; };fn(); // Only std::move_only_function can do
}
因为在 C++ 中,一个只提供了复制构造/复制赋值、并且没有删除对应的移动语义版本的类型,会隐式地可移动(实际上还是复制),但只提供了移动构造/移动赋值的类型就只能被移动;所以可以将“可移动”视作是一个更宽泛地构造与赋值约束。
因此 std::move_only_function
在可存储的类型种类数量上要多于 std::function
。
除了语义上的差别,std::move_only_function
还有一点与 std::function
不同:std::move_only_function
可以将 const
、reference 修饰符和 noexcept
加到模板参数列表中,使得它的 operator()()
方法也具有对应的修饰符和限定符。
#include <cstdint>
#include <functional>
#include <iostream>uint64_t count_digits( int64_t num ) noexcept
{uint64_t cnt = 0;while ( num != 0 ) {num /= 10;++cnt;}return cnt;
}int main()
{std::move_only_function<uint64_t( int64_t ) const noexcept> fn;// 先生,你也不想不被别人知道你用了一个不可变的 noexcept 函数吧fn = count_digits;std::cout << fn( 114514 ) << std::endl;
}
这一点直接导致:如果不在 std::move_only_function
的参数列表中添加 const
修饰,那么对应的 operator()()
就不会像 std::function
一样默认带有 const
修饰;这有可能在一些隐式 const
语境下触发令人困惑的编译报错。
本意是好的(
那么这种泛型包装器是怎么实现的?
1.设计
答案是运行期多态;不过这种多态并不是特指使用虚函数实现的多态,多态只是一种表现形式,我们有很多种方式可以实现它。
不过多态当然可以选择使用虚函数机制,就像下面这样:
#include <cstdint>
#include <type_traits>
#include <utility>template<typename... Signatures>
class UniqueFunction;
template<typename Ret, typename... Args>
class UniqueFunction<Ret( Args... )> {struct AnyFn {virtual ~AnyFn() noexcept = default;virtual Ret operator()( Args... args ) = 0;virtual void move_to( void* const dest_mem ) noexcept = 0;};template<typename Fn, typename = void>struct FnContainer final : public AnyFn {private:static_assert( !std::is_reference<Fn>::value, "Incomplete type" );Fn fntor_;public:FnContainer( Fn fntor ) noexcept( std::is_nothrow_move_constructible<Fn>::value ): fntor_ { std::move( fntor ) }{}FnContainer( const FnContainer& ) = delete;FnContainer& operator=( const FnContainer& ) & = delete;virtual ~FnContainer() noexcept = default;Ret operator()( Args... args )noexcept( noexcept( std::declval<Fn>()( std::declval<Args>()... ) ) ) override{return fntor_( std::forward<Args>( args )... );}void move_to( void* const dest_mem ) noexcept override{new ( dest_mem ) FnContainer( std::move( fntor_ ) );}};
#if __cplusplus >= 201402Ltemplate<typename Fn> // Empty Base Optimizationstruct FnContainer<Fn, std::enable_if_t<std::is_empty<Fn>::value && !std::is_final<Fn>::value>> final: public AnyFn, private Fn {FnContainer( Fn fntor ) noexcept( std::is_nothrow_move_constructible<Fn>::value ): Fn( std::move( fntor ) ){}FnContainer( const FnContainer& ) = delete;FnContainer& operator=( const FnContainer& ) & = delete;virtual ~FnContainer() noexcept = default;Ret operator()( Args... args )noexcept( noexcept( std::declval<Fn>()( std::declval<Args>()... ) ) ) override{return ( ( static_cast<Fn&>( *this ) ) )( std::forward<Args>( args )... );}void move_to( void* const dest_mem ) noexcept override{new ( dest_mem ) FnContainer( std::move( static_cast<Fn&>( *this ) ) );}};
#endifunion {std::add_pointer_t<Ret( Args... )> fptr_;AnyFn* ftor_;} data_;enum class Tag : std::uint8_t { None, Fptr, FtorInline, FtorDync } tag_;public:// Implementation ...
};
为了支持小对象优化(SOO),这种实现方式存在几个缺点:依赖无关的类型包装、在 C++11 中不能启用 EBO(空基类优化)、无论如何都要支付一个虚指针的大小,而且关键是:对于 std::move_only_function
的不同函数类型特化,这个实现都需要完整地重复编写一遍代码,这太粗暴了。
不过 libstdc++ 选择了这种粗暴的实现:他们在一个头文件里写了个完整的
std::move_only_function
,然后反复 include 这个头文件为不同函数类型提供特化实现。
评价是神人。
因此我们需要将内部的可调用目标存储、调用支持和对外接口实现分离。
2.实现
2.1 对象存储
没有继承结构配合虚函数机制,如何实现运行期多态?
很简单,手写虚表不就是了。
虚表负责多态实现,此处的多态需要涵盖四种操作:构造、移动、析构和调用;显而易见的是,构造需要绑定到对应的类型上,并且由该类型实际决定如何实现移动、析构以及调用操作;所以构造不需要被写在虚表中,它实际应该是一个独立的模板函数,用以初始化虚表。
调用操作比较微妙:这一操作与对外接口中的模板参数强相关,所以没法直接在虚表中写出对应的调用操作指针类型。
但这不是问题,我们可以使用 CRTP(奇异递归模板)将这个调用操作的类型交给派生类编写,只需要在基类中引用它就好了。
由于采用 SOO 时需要使用 placement new 等在与实际类型无关的内存区域上构造对应类型,因此代码中会需要使用一些比较神必的 C++ 操作,如
std::launder
,这些内容的使用超出了本文的范畴,因此不会过多介绍。
读者只需要知道:在 C++17 之后,如果希望从一个std::byte
或unsigned char
内存区域上使用reinterpret_cast
“提取”一个无关类型指针,那么必须将提取得到的指针丢给std::launder
并使用它的返回值,否则会违反对象生命周期模型和存储重用规则导致 UB。
而且与真实标准库的代码相比,本文不会涉及与内存对齐有关的内容,因此代码不会考虑类型的实际对齐要求。
std::move_only_function
的构造函数会使用非初始化列表的方式(也就是不使用花括号语法),配合 std::forward
转发参数构造目标;所以在存储实现中,构造 target 时也需要这样做。
于是可以得到以下代码:
#include <cassert>
#include <memory>namespace util {template<typename To, typename From>To* launder_as( From* ptr ) noexcept{
#if __cplusplus >= 201703Lreturn std::launder( reinterpret_cast<To*>( ptr ) );
#elsereturn reinterpret_cast<To*>( ptr );
#endif}
} // namespace utilnamespace wrapper {template<typename Derived>class FnStorageBlock {protected:
#if __cplusplus >= 201703Lusing Data = std::byte;
#elseusing Data = unsigned char;
#endifunion AnyFn {Data buf_[sizeof( void* ) * 2];Data* dptr_;};struct VTable final {const typename Derived::Invoker invoke;const typename std::add_pointer<void( AnyFn& ) noexcept>::type destroy;const typename std::add_pointer<void( AnyFn& dst, AnyFn& src ) noexcept>::type move;};template<typename T>using Inlinable = std::integral_constant<bool,( ( sizeof( AnyFn::buf_ ) <= sizeof( T ) )&& std::is_nothrow_move_constructible<T>::value )>;const VTable* vtable_;AnyFn callee_;static void destroy_null( AnyFn& ) noexcept {}static void move_null( AnyFn&, AnyFn& ) noexcept {}template<typename T>static void destroy_inline( AnyFn& fn ) noexcept{const auto ptr = util::launder_as<T>( fn.buf_ );ptr->~T();}template<typename T>static void move_inline( AnyFn& dst, AnyFn& src ) noexcept{new ( &dst ) T( std::move( *util::launder_as<T>( src.buf_ ) ) );destroy_inline<T>( src );}template<typename T>static void destroy_dynamic( AnyFn& fn ) noexcept{const auto dptr = util::launder_as<T>( fn.dptr_ );dptr->~T();operator delete( fn.dptr_ );}template<typename T>static void move_dynamic( AnyFn& dst, AnyFn& src ) noexcept{dst.dptr_ = src.dptr_;src.dptr_ = nullptr;}template<typename F>static typename std::enable_if<Inlinable<typename std::decay<F>::type>::value>::typestore_fn( const VTable*( &vtable ), AnyFn& any, F&& fn ) noexcept{using T = typename std::decay<F>::type;const auto _location = new ( &any.buf_ ) T( std::forward<F>( fn ) );assert( static_cast<void*>( _location ) == static_cast<void*>( &any ) );vtable = &table_inline<T>();}template<typename F>static typename std::enable_if<!Inlinable<typename std::decay<F>::type>::value>::typestore_fn( const VTable*( &vtable ), AnyFn& any, F&& fn ) noexcept( false ){using T = typename std::decay<F>::type;auto dptr = std::unique_ptr<void, void ( * )( void* )>(operator new( sizeof( T ) ),+[]( void* ptr ) { operator delete( ptr ); } );const auto location = new ( dptr.get() ) T( std::forward<F>( fn ) );assert( static_cast<void*>( location ) == dptr.get() );any.dptr_ = static_cast<Data*>( dptr.release() );vtable = &table_dynamic<T>();}template<typename T>static const VTable& table_inline() noexcept{static const VTable tbl { Derived::template invoke_inline<T>, destroy_inline<T>, move_inline<T> };return tbl;}template<typename T>static const VTable& table_dynamic() noexcept{static const VTable tbl { Derived::template invoke_dynamic<T>, destroy_dynamic<T>, move_dynamic<T> };return tbl;}static const VTable& table_null() noexcept{static const VTable tbl { Derived::invoke_null, destroy_null, move_null };return tbl;}constexpr FnStorageBlock() noexcept : vtable_ { &table_null() } {}void reset() noexcept{vtable_->destroy( callee_ );vtable_ = &table_null();}template<typename F>void reset( F&& fn ) noexcept( Inlinable<typename std::decay<F>::type>::value ){const VTable* vtable = nullptr;AnyFn tmp;store_fn( vtable, tmp, std::forward<F>( fn ) );reset();std::swap( vtable_, vtable );vtable_->move( callee_, tmp );}public:FnStorageBlock( const FnStorageBlock& ) = delete;FnStorageBlock& operator=( const FnStorageBlock& ) & = delete;FnStorageBlock( FnStorageBlock&& rhs ) noexcept : vtable_ { rhs.vtable_ }{vtable_->move( callee_, rhs.callee_ );rhs.vtable_ = &table_null();}FnStorageBlock& operator=( FnStorageBlock&& rhs ) & noexcept{reset();std::swap( vtable_, rhs.vtable_ );vtable_->move( callee_, rhs.callee_ );return *this;}~FnStorageBlock() noexcept { reset(); }void swap( FnStorageBlock& lhs ) noexcept{AnyFn tmp;vtable_->move( tmp, callee_ );lhs.vtable_->move( callee_, lhs.callee_ );vtable_->move( lhs.callee_, tmp );std::swap( vtable_, lhs.vtable_ );}friend void swap( FnStorageBlock& a, FnStorageBlock& b ) noexcept { return a.swap( b ); }friend constexpr bool operator==( const FnStorageBlock& a, std::nullptr_t ) noexcept{return !static_cast<bool>( a );}friend constexpr bool operator!=( const FnStorageBlock& a, std::nullptr_t ) noexcept{return static_cast<bool>( a );}constexpr explicit operator bool() const noexcept { return vtable_ != &table_null(); }};
} // namespace wrapper
2.2 对象调用
前文说过,std::move_only_function
会将函数类型的 cref 以及 noexcept
加到 operator()()
方法上,这也表示在调用 target 时,也需要使用相同的引用类型。
所以还需要一个类似于 std::forward
的含参转发函数:
// 与 std::forward 不同,这里的 Cref 与实际类型 T 无关,它只负责携带 cref 信息
template<typename Cref, typename T>
constexpr typename std::enable_if<!std::is_reference<Cref>::value,typename std::conditional<std::is_const<typename std::remove_reference<Cref>::type>::value, const T&&, T&&>::type>::typecref_fwd( T&& param ) noexcept
{return std::forward<T>( param );
}
template<typename Cref, typename T>
constexpr typename std::enable_if<std::is_lvalue_reference<Cref>::value,typename std::conditional<( std::is_const<typename std::remove_reference<Cref>::type>::value|| std::is_rvalue_reference<T&&>::value ),const typename std::remove_reference<T>::type&,typename std::remove_reference<T>::type&>::type>::typecref_fwd( T&& param ) noexcept
{return param;
}
template<typename Cref, typename T>
constexpr typename std::enable_if<std::is_rvalue_reference<Cref>::value,typename std::conditional<std::is_const<typename std::remove_reference<Cref>::type>::value,const typename std::remove_reference<T>::type&&,typename std::remove_reference<T>::type>::type&&>::typecref_fwd( T&& param ) noexcept
{return std::move( param );
}
这个函数的作用就是根据 Cref
中携带的 cref 信息,将参数 param
处理为对应的类型;实际并不关心 Cref
的具体类型。
此外,因为函数包装器要支持非常多的可调用目标,这些目标涵盖了从函数指针、函数对象、方法指针再到成员指针多个类型。
这其中最特殊的是方法指针和成员指针,它们的调用方式与常规函数指针和函数对象迥然不同;所以在调用 target 时还需要根据它的实际类型做不同区分,否则就会触发语法错误。
但很显然不可能直接在代码中编写这么多的分支选择代码,这会混淆功能实现和辅助代码的界限。很巧的是,标准库的 std::invoke
提供的就是我们需要的功能:使用同一个语法调用需要不同调用形式的可调用目标。
很可惜,std::invoke
只适用于 C++17 后的标准,不过这不要紧,手写一个也不是什么大问题:
#include <type_traits>
#if __cplusplus >= 201703L
# include <functional>
#endifnamespace trait {// 因为要特殊处理 std::reference_wrapper 类型,所以需要有一个辅助谓词模板以判断某个类型是否是 std::reference_wrapper 的实例化版本template<typename Instance, template<typename...> class Tmp>struct InstanceOf {private:template<typename... Args>static constexpr std::true_type check( const Tmp<Args...>& );static constexpr std::false_type check( ... );public:static constexpr bool value =( !std::is_reference<Instance>::value&& decltype( check( std::declval<typename std::remove_cv<Instance>::type>() ) )::value );};
} // namespace traitnamespace util {
#if __cplusplus >= 201703Ltemplate<typename Fn, typename... Args>constexpr decltype( auto ) invoke( Fn&& fn, Args&&... args )noexcept( std::is_nothrow_invocable_v<Fn, Args...> ){return std::invoke( std::forward<Fn>( fn ), std::forward<Args>( args )... );}
#elsetemplate<typename C, typename MemFn, typename Object, typename... Args>constexpr auto invoke( MemFn C::*method, Object&& object, Args&&... args )noexcept( noexcept( ( std::forward<Object>( object ).*method )( std::forward<Args>( args )... ) ) ) ->typename std::enable_if<std::is_member_function_pointer<MemFn C::*>::value&& ( std::is_base_of<C, typename std::decay<Object>::type>::value|| std::is_same<C, typename std::decay<Object>::type>::value ),decltype( ( std::forward<Object>( object ).*method )( std::forward<Args>( args )... ) )>::type{return ( std::forward<Object>( object ).*method )( std::forward<Args>( args )... );}template<typename C, typename MemFn, typename Object, typename... Args>constexpr auto invoke( MemFn C::*method, Object&& object, Args&&... args )noexcept( noexcept( ( object.get().*method )( std::forward<Args>( args )... ) ) ) ->typename std::enable_if<( std::is_member_function_pointer<MemFn C::*>::value&& trait::InstanceOf<typename std::decay<Object>::type, std::reference_wrapper>::value ),decltype( ( object.get().*method )( std::forward<Args>( args )... ) )>::type{return ( object.get().*method )( std::forward<Args>( args )... );}template<typename C, typename MemFn, typename Object, typename... Args>constexpr auto invoke( MemFn C::*method, Object&& object, Args&&... args )noexcept( noexcept( ( ( *std::forward<Object>( object ) ).*method )( std::forward<Args>( args )... ) ) )-> typename std::enable_if<( std::is_member_function_pointer<MemFn C::*>::value&& !( std::is_base_of<C, typename std::decay<Object>::type>::value|| std::is_same<C, typename std::decay<Object>::type>::value|| trait::InstanceOf<typename std::decay<Object>::type, std::reference_wrapper>::value ) ),decltype( ( ( *std::forward<Object>( object ) ).*method )( std::forward<Args>( args )... ) )>::type{return ( ( *std::forward<Object>( object ) ).*method )( std::forward<Args>( args )... );}template<typename C, typename MemObj, typename Object>constexpr auto invoke( MemObj C::*member, Object&& object ) noexcept ->typename std::enable_if<( std::is_member_object_pointer<MemObj C::*>::value&& ( std::is_base_of<C, typename std::decay<Object>::type>::value|| std::is_same<C, typename std::decay<Object>::type>::value ) ),decltype( std::forward<Object>( object ).*member )>::type{return std::forward<Object>( object ).*member;}template<typename C, typename MemObj, typename Object>constexpr auto invoke( MemObj C::*member, Object&& object ) noexcept ->typename std::enable_if<( std::is_member_object_pointer<MemObj C::*>::value&& trait::InstanceOf<typename std::decay<Object>::type, std::reference_wrapper>::value ),decltype( object.get().*member )>::type{return object.get().*member;}template<typename C, typename MemObj, typename Object>constexpr auto invoke( MemObj C::*member, Object&& object )noexcept( noexcept( ( *std::forward<Object>( object ) ).*member ) ) ->typename std::enable_if<( std::is_member_object_pointer<MemObj C::*>::value&& !( std::is_base_of<C, typename std::decay<Object>::type>::value|| std::is_same<C, typename std::decay<Object>::type>::value|| trait::InstanceOf<typename std::decay<Object>::type, std::reference_wrapper>::value ) ),decltype( ( *std::forward<Object>( object ) ).*member )>::type{return ( *std::forward<Object>( object ) ).*member;}template<typename Fn, typename... Args>constexpr auto invoke( Fn&& fn, Args&&... args )noexcept( noexcept( std::forward<Fn>( fn )( std::forward<Args>( args )... ) ) ) ->typename std::enable_if<!( std::is_member_function_pointer<typename std::remove_reference<Fn>::type>::value|| std::is_member_object_pointer<typename std::remove_reference<Fn>::type>::value ),decltype( std::forward<Fn>( fn )( std::forward<Args>( args )... ) )>::type{return std::forward<Fn>( fn )( std::forward<Args>( args )... );}
#endif
}
因为 move_only_function
要支持带 cref 的函数类型,但是又没法使用除了特化匹配的方法从函数类型中提取这些限定符信息,所以在负责实现调用操作的派生类型中,我们需要有个 Dummy 模板参数以承载函数类型的 cref 信息。
结合前文的辅助代码,这个派生类最终长这样:
namespace trait {
#if __cplusplus >= 201703Ltemplate<typename R, typename Fn, typename... ArgTypes>using Invocable_r = std::is_invocable_r<R, Fn, ArgTypes...>;template<typename R, typename Fn, typename... ArgTypes>using NothrowInvocable_r = std::is_nothrow_invocable_r<R, Fn, ArgTypes...>;
#elsetemplate<typename R, typename Fn, typename... ArgTypes>struct Invocable_r {private:template<typename F>static constexpr auto check( F&& fn ) ->typename std::enable_if< // 依赖于前文实现的 invoke 模板函数做编译期表达式校验std::is_convertible<decltype( util::invoke( std::forward<F>( fn ), std::declval<ArgTypes>()... ) ),R>::value,std::true_type>::type;static constexpr std::false_type check( ... );public:static constexpr bool value = decltype( check( std::declval<Fn>() ) )::value;};template<typename R, typename Fn, typename... ArgTypes>struct NothrowInvocable_r {private:template<typename F>static constexpr auto check( F&& fn ) ->typename std::integral_constant<bool,noexcept( static_cast<R>( util::invoke( std::forward<F>( fn ), std::declval<ArgTypes>()... ) ) )>;static constexpr std::false_type check( ... );public:static constexpr bool value = decltype( check( std::declval<Fn>() ) )::value;};
#endif
}namespace wrapper {// `CrefInfo` can be any types that contains the `cref` info of the functor.// e.g. For the function type `void () const&`, the `CrefInfo` can be: `const int&`.template<typename CrefInfo, typename R, bool Noexcept, typename... Args>class FnInvokeBlock : public FnStorageBlock<FnInvokeBlock<CrefInfo, R, Noexcept, Args...>> {friend class FnStorageBlock<FnInvokeBlock>;using typename FnStorageBlock<FnInvokeBlock>::AnyFn;template<typename T>using Param_t = typename std::conditional<std::is_scalar<T>::value, T, T&&>::type;template<typename T>using Fn_t = typename std::conditional<std::is_const<typename std::remove_reference<CrefInfo>::type>::value, const T, T>::type;template<typename T>using Callee_t = typename std::conditional<std::is_rvalue_reference<CrefInfo>::value,typename std::add_rvalue_reference<T>::type,T>::type;template<typename Cref, typename T>static constexpr typename std::enable_if<!std::is_reference<Cref>::value,typename std::conditional<std::is_const<typename std::remove_reference<Cref>::type>::value,const T&&,T&&>::type>::typecref_fwd( T&& param ) noexcept{return std::forward<T>( param );}template<typename Cref, typename T>static constexpr typename std::enable_if<std::is_lvalue_reference<Cref>::value,typename std::conditional<( std::is_const<typename std::remove_reference<Cref>::type>::value|| std::is_rvalue_reference<T&&>::value ),const typename std::remove_reference<T>::type&,typename std::remove_reference<T>::type&>::type>::typecref_fwd( T&& param ) noexcept{return param;}template<typename Cref, typename T>static constexpr typename std::enable_if<std::is_rvalue_reference<Cref>::value,typename std::conditional<std::is_const<typename std::remove_reference<Cref>::type>::value,const typename std::remove_reference<T>::type&&,typename std::remove_reference<T>::type>::type&&>::typecref_fwd( T&& param ) noexcept{return std::move( param );}protected:using Invoker = typename std::add_pointer<R( Fn_t<AnyFn>&, Param_t<Args>... ) noexcept( Noexcept )>::type;static R invoke_null( Fn_t<AnyFn>&, Param_t<Args>... ) noexcept( Noexcept ){std::abort();// The standard says this should trigger an undefined behavior.}template<typename T>static R invoke_inline( Fn_t<AnyFn>& fn, Param_t<Args>... args ) noexcept( Noexcept ){const auto ptr = util::launder_as<Fn_t<T>>( fn.buf_ );return util::invoke( cref_fwd<CrefInfo>( *ptr ), std::forward<Args>( args )... );}template<typename T>static R invoke_dynamic( Fn_t<AnyFn>& fn, Param_t<Args>... args ) noexcept( Noexcept ){const auto dptr = util::launder_as<Fn_t<T>>( fn.dptr_ );return util::invoke( cref_fwd<CrefInfo>( *dptr ), std::forward<Args>( args )... );}constexpr FnInvokeBlock() = default;public:using result_type = R;};
}
此时 CRTP 就能够将一些必须类型反向注入回基类中。
2.3 对外接口
最后是对外接口,也就是 std::move_only_function
的近似实现 UniqueFunction
。
需要注意的是,std::move_only_function
的构造函数对于 target 的类型 T
(这里要把 T
理解为模板参数中的万能引用类型)有特殊要求:target 不必满足 std::is_move_constructible<std::decay_t<T>>
,但必须满足 std::is_constructible<std::decay_t<T>, T>
;而且在构造时,会使用非初始化列表的方式配合 std::forward
构造类型 std::decay_t<T>
也就是说 target 不需要满足可移动构造,但必须能够被以 std::decay_t<T>( std::forward<T>( T ) )
的方式构造。
而且 target 在被调用时,调用表达式必须能够满足模板参数中的函数类型。
对于违反以上约束的情况,标准说程序非良构;所以这里可以尽情发挥。
简单点的话可以使用 SFINAE 拒绝掉这些情况:
template<typename...>
class UniqueFunction;
template<typename R, typename... Args>
class UniqueFunction<R( Args... )> : public detail::wrapper::FnInvokeBlock<int, R, false, Args...> {using Base = detail::wrapper::FnStorageBlock<detail::wrapper::FnInvokeBlock<int, R, false, Args...>>;public:constexpr UniqueFunction() = default;constexpr UniqueFunction( std::nullptr_t ) noexcept : UniqueFunction() {}template<typename F,typename = typename std::enable_if<std::is_constructible<typename std::decay<F>::type, F>::value&& detail::trait::Invocable_r<R, F, Args...>::value>::type>UniqueFunction( F&& fn ) noexcept( Base::template Inlinable<typename std::decay<F>::type>::value ){Base::store_fn( this->vtable_, this->callee_, std::forward<F>( fn ) );}// In C++11, `std::in_place_type` does not exist, and we will not use it either.// Therefore, we do not provide an overloaded constructor for this type here.template<typename F>typename std::enable_if<std::is_constructible<typename std::decay<F>::type, F>::value,UniqueFunction&>::typeoperator=( F&& fn ) & noexcept( Base::template Inlinable<typename std::decay<F>::type>::value ){this->reset( std::forward<F>( fn ) );return *this;}UniqueFunction& operator=( std::nullptr_t ) noexcept{this->reset();return *this;}R operator()( Args... args ){return ( *this->vtable_->invoke )( this->callee_, std::forward<Args>( args )... );}
};
以上只是一个基础情况(不包含任何 cref 和 noexcept
修饰),但这个基础情况本身非常轻量,可以通过简单地复制粘贴就为不同函数类型提供特化实现。
这里用宏辅助一下:
#define UNIQUE_FUNCTION( Const, Ref, Noexcept ) \template<typename R, typename... Args> \class UniqueFunction<R( Args... ) Const Ref noexcept( Noexcept )> \: public detail::wrapper::FnInvokeBlock<Const int Ref, R, Noexcept, Args...> { \using Base = \detail::wrapper::FnStorageBlock<detail::wrapper::FnInvokeBlock<Const int Ref, R, Noexcept, Args...>>; \\public: \constexpr UniqueFunction() = default; \constexpr UniqueFunction( std::nullptr_t ) noexcept : UniqueFunction() {} \template<typename F, \typename = typename std::enable_if< \std::is_constructible<typename std::decay<F>::type, F>::value \&& detail::trait::Invocable_r<R, F, Args...>::value \&& ( !Noexcept || detail::trait::NothrowInvocable_r<R, F, Args...>::value )>::type> \UniqueFunction( F&& fn ) noexcept( Base::template Inlinable<typename std::decay<F>::type>::value ) \{ \Base::store_fn( this->vtable_, this->callee_, std::forward<F>( fn ) ); \} \template<typename F> \typename std::enable_if<std::is_constructible<typename std::decay<F>::type, F>::value \&& detail::trait::Invocable_r<R, F, Args...>::value \&& ( !Noexcept || detail::trait::NothrowInvocable_r<R, F, Args...>::value ), \UniqueFunction&>::type \operator=( F&& fn ) & noexcept( Base::template Inlinable<typename std::decay<F>::type>::value ) \{ \this->reset( std::forward<F>( fn ) ); \return *this; \} \UniqueFunction& operator=( std::nullptr_t ) noexcept \{ \this->reset(); \return *this; \} \\R operator()( Args... args ) Const Ref noexcept( Noexcept ) \{ \return ( *this->vtable_->invoke )( this->callee_, std::forward<Args>( args )... ); \} \}
然后展开:
UNIQUE_FUNCTION(, , false );
UNIQUE_FUNCTION(, &, false );
UNIQUE_FUNCTION(, &&, false );
UNIQUE_FUNCTION( const, , false );
UNIQUE_FUNCTION( const, &, false );
UNIQUE_FUNCTION( const, &&, false );
#if __cplusplus >= 201703L
UNIQUE_FUNCTION(, , true );
UNIQUE_FUNCTION(, &, true );
UNIQUE_FUNCTION(, &&, true );
UNIQUE_FUNCTION( const, , true );
UNIQUE_FUNCTION( const, &, true );
UNIQUE_FUNCTION( const, &&, true );
#endif
现在就得到了一个相对完整的 std::move_only_function
替换实现。
测试一下:
#include <functional>
#include <iostream>struct A {double floating;void operator()() const noexcept { std::cout << floating << std::endl; }double get() && noexcept { return floating; }
};double foo( const A& obj, int factor ) noexcept
{return obj.floating * factor;
}int main()
{
#if __cplusplus >= 201402L{UniqueFunction<void() const> fn;auto ptr = std::make_unique<int>( 42 );fn = [res = std::move( ptr )]() { std::cout << *res << std::endl; };fn();}
#endif{UniqueFunction<void()> fn;assert( fn == nullptr );}{UniqueFunction<double( const A&, int ) const noexcept> fn { &foo };A a { 3.14 };const auto result = fn( a, 2 );assert( result == 3.14 * 2 );std::cout << result << std::endl;}{UniqueFunction<void() const noexcept> fn { A { 3.14 } };fn();}{UniqueFunction<double( A&& ) && noexcept> fn;fn = &A::get;A a { 3.14 };const auto result = std::move( fn )( std::move( a ) );assert( result == 3.14 );std::cout << result << std::endl;}{UniqueFunction<double( A&& ) && noexcept> fn;fn = std::mem_fn( &A::get );A a { 3.14 };const auto result = std::move( fn )( std::move( a ) );assert( result == 3.14 );std::cout << result << std::endl;}{UniqueFunction<double( A& ) const noexcept> fn;fn = &A::floating;A a { 3.14 };const auto result = fn( a );assert( result == 3.14 );std::cout << result << std::endl;}{UniqueFunction<void()> fn1 = [] { std::cout << "moved" << std::endl; };UniqueFunction<void()> fn2 = std::move( fn1 );assert( fn1 == nullptr );fn2();}{UniqueFunction<void()> fn = [] { std::cout << "reset" << std::endl; };fn = nullptr;assert( fn == nullptr );}
}
3.总结
得益于职责分离的类型设计,前文给出的代码只需要为虚表中添加一个复制操作、同时递归地提供复制构造和复制赋值支持,那么同样可以实现一个 C++26 的 std::copyable_function
。
其实本文的灵感很大程度上参考自 MSVC STL 的实现(
本文的所有代码均可以在此找到。