2511C++,CTAD简化回调
原文
是否可用CTAD来简化WRL的回调函数的使用
Bwmat问,“CTAD能否避免在最后一例中需要冗长的类型名”,这是对显式特化中相当冗长的类型的响应.
void MyClass::RegisterCompletion(ABI::IAsyncAction* action)
{m_showingToken = inputPane->put_Showing(Microsoft::WRL::Callback<ABI::ITypedEventHandler<//<<ABI::InputPane*,ABI::InputPaneVisibilityEventArgs*>>(//>>this, &MyClass::OnInputPaneShowing).Get());
}
现在,不能在此真正使用CTAD(推导类模板参数),因为回调不是一个类.
类模板参数推导,是为了改进类命名中,缺少推导类型.但是函数确实有推导类型!所以来使用它.
从此开始:
template<typename TDelegateInterface,typename TCallbackObject,typename... TArgs
>
ComPtr<TDelegateInterface> Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
想推导TDelegateInterface为TypedEventHandler<TArgs...>:
template<typename TDelegateInterface= TypedEventHandler<TArgs...>,//..typename TCallbackObject,typename... TArgs
>
ComPtr<TDelegateInterface> Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
可惜,这不管用,因为模板默认参数不能引用未来的模板参数.
可试重排参数.
template<typename TCallbackObject,//<<typename... TArgs,typename TDelegateInterface= TypedEventHandler<TArgs...>//>>
>
ComPtr<TDelegateInterface> Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
但是,这不管用,因为模板参数包必须在模板参数列表的尾.
可按专门有两个TArg和一个其他情况来分割模板.
template<typename TDelegateInterface,//<<typename TCallbackObject,typename... TArgs,//>>
>
ComPtr<TDelegateInterface> Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
template<typename TCallbackObject,//<<typename TArg1, typename TArg2,typename TDelegateInterface =TypedEventHandler<TArg1, TArg2>//>>
>
ComPtr<TDelegateInterface> Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
可惜,因为调用歧义,这失败了.当回调的参数数量恰好是两个时,必须删除第一个.
template<typename TDelegateInterface,typename TCallbackObject,typename... TArgs,
>
std::enable_if_t<sizeof...(TArgs) != 2,//..ComPtr<TDelegateInterface>>
Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
template<typename TCallbackObject,typename TArg1, typename TArg2,typename TDelegateInterface =TypedEventHandler<TArg1, TArg2>
>
ComPtr<TDelegateInterface> Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
但即使使用该版本,双参回调很挫:如果想指定一个自定义闭包接口(比如,比如SuspendingEventHandler),你必须费力地完成前三个参数,这样才能最终指定最后参数.
Callback<MyObject, IInspectable*, SuspendingEventArgs*, SuspendingEventHandler>(//..this, &MyObject::OnSuspending);
相反可用未来的类型推导.
template<typename TDelegateInterface = void,//..typename TCallbackObject,typename... TArgs
>
ComPtr<std::conditional_t<std::is_same_v<TDelegateInterface, void>,TypedEventHandler<TArgs...>,TDelegateInterface>>
Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
但是等等,TypedEventHandler只接受两个模板参数,因此如果使用仅接受一个参数的成员函数指针调用回调,会收到编译器错误,因为它无法构建TypedEventHandler<TArg>.
struct MyObject
{HRESULT OnUIInvoked(IUICommand* command);
};
Callback<UICommandInvokedHandler<(this, &MyObject::OnUIInvoked);
//^^^错误:`模板参数`推导/替换失败,`TypedEventHandler`的`模板参数`数量错误
必须延迟提及TypedEventHandler<TArgs...>,直到开始使用它.
template<typename... TArgs>//<<
struct TypedEventHandlerHolder
{using type = TypedEventHandler<TArgs...>;
};//>>template<typename TDelegateInterface = void,typename TCallbackObject,typename... TArgs
>
ComPtr<typename std::conditional_t<std::is_same_v<TDelegateInterface, void>,TypedEventHandlerHolder<TArgs...>,//..std::type_identity<TDelegateInterface>>::type//..
>
Callback(_In_ TCallbackObject *object,_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
现在,回到顶部,我提到CTAD不适用,因为CTAD针对类模板,但回调是一个函数模板.
但是,如果把回调变成类模板呢?
template<typename TDelegateInterface,typename TCallbackObject,//..typename... TArgs
>
struct Callback : ComPtr<TDelegateInterface>
{Callback(TCallbackObject *object,HRESULT (TCallbackObject::* method)(TArgs...));
};
现在,CTAD将推导TCallbackObject和TArgs,可用推导指南来推导TDelegateInterface.
template<typename TCallbackObject,typename TArg1, TArg2
>
Callback(TCallbackObject*,HRESULT (TCallbackObject::*)(TArg1, TArg2))-> Callback<TypedEventHandler<TArg1, TArg2>, TCallbackObject, TArg1, TArg2>;
可惜,CTAD不适合部分特化,因此无法如下:
Callback<SuspendingEventHandler>(this, &MyObject::OnSuspending);
所以我想,被困在从未来推导类型的重载函数中.
但实际上,制胜之举是不上场.
与其试使回调优雅,不如创建单独的TypedEventHandlerCallback函数.
template<typename TCallbackObject,typename TArg1, typename TArg2>
ComPtr<TypedEventHandler<TArg1, TArg2>>TypedEventHandlerCallback(TCallbackObject* object,HRESULT (TCallbackObject::*method)(TArg1, TArg2));
则原始代码将是
void MyClass::RegisterCompletion(ABI::IAsyncAction* action)
{m_showingToken = inputPane->put_Showing(TypedEventCallback(this, &MyClass::OnInputPaneShowing).Get());
}
此时,还可创建个EventHandlerCallback函数.
template<typename TCallbackObject,typename TArg>
ComPtr<EventHandler<TArg>>
EventHandlerCallback(TCallbackObject* object,HRESULT (TCallbackObject::*method)(IInspectable*, TArg));
