Commenter Bwmat asked, “Can CTAD avoid the need for the lengthy type name in the last example?” This was in response to the rather lengthy type in the explicit specialization
void MyClass::RegisterCompletion(ABI::IAsyncAction* action)
{
m_showingToken = inputPane->put_Showing(
Microsoft::WRL::Callback<ABI::ITypedEventHandler<
ABI::InputPane*,
ABI::InputPaneVisibilityEventArgs*>>(
this, &MyClass::OnInputPaneShowing).Get());
}
Now, you cannot literally use CTAD (Class Template Argument Deduction) here because Callback is not a class.
Class template argument deduction exists to cover for the lack of type deduction in class naming. But functions do have type deduction! So let’s use it.
We’re starting with this:
template< typename TDelegateInterface, typename TCallbackObject, typename… TArgs
ComPtr<TDelegateInterface> Callback( In TCallbackObject object, In HRESULT (TCallbackObject:: method)(TArgs…) );
We want to deduce the TDelegateInterface to be TypedEventHandler<TArgs…>:
template< typename TDelegateInterface = TypedEventHandler<TArgs…>, typename TCallbackObject, typename… TArgs
ComPtr<TDelegateInterface> Callback( In TCallbackObject object, In HRESULT (TCallbackObject:: method)(TArgs…) );
Unfortunately, this doesn’t work because template default parameters cannot refer to future template parameters.
We could try reordering the parameters.
template<
typename TCallbackObject,
typename… TArgs,
typename TDelegateInterface
= TypedEventHandler<TArgs…>
ComPtr<TDelegateInterface> Callback( In TCallbackObject object, In HRESULT (TCallbackObject:: method)(TArgs…) );
However, this doesn’t work because template parameter packs must come at the end of the template parameter list.
We can finesse the problem by splitting the template into one that specifically has exactly two TArgs and one for the other cases.
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…) );
Unfortunately, this fails due to the ambiguous call. We have to remove the first one from consideration when the number of parameters to the callback is exactly two.
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…) );
But even with this version, the two-argument callback has poor ergonomics: If you want to specify a custom delegate interface (like, say, SuspendingEventHandler), you have to slog through the first three parameters so you can finally specify the last one.
Callback<MyObject, IInspectable*, SuspendingEventArgs*, SuspendingEventHandler>( this, &MyObject::OnSuspending);
Instead, we can use type deduction from the future.
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…) );
But wait, the TypedEventHandler takes only two template arguments, so if we invoke Callback with a pointer to member function that takes only one argument, we get a compiler error because it cannot form TypedEventHandler<TArg>.
struct MyObject { HRESULT OnUIInvoked(IUICommand* command); };
Callback<UICommandInvokedHandler<(this, &MyObject::OnUIInvoked); // ^^^ error: template argument deduction/substitution failed // wrong number of template arguments to TypedEventHandler
We have to delay the mention of TypedEventHandler<TArgs…> until we are committed to using it.
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…) );
Now, back at the top, I mentioned that CTAD doesn’t apply because CTAD is for class templates, but Callback is a function template.
But what if we made Callback a class template?
template< typename TDelegateInterface, typename TCallbackObject, typename… TArgs
struct Callback : ComPtr<TDelegateInterface> { Callback(TCallbackObject object, HRESULT (TCallbackObject:: method)(TArgs…)); };
Now, CTAD will infer the TCallbackObject and TArgs, and we can use a deduction guide to infer the TDelegateInterface.
template< typename TCallbackObject, typename TArg1, TArg2
Callback(TCallbackObject*, HRESULT (TCallbackObject::*)(TArg1, TArg2)) -> Callback<TypedEventHandler<TArg1, TArg2>, TCallbackObject, TArg1, TArg2>;
Unfortunately, CTAD doesn’t work with partial specialization, so you can’t write
Callback<SuspendingEventHandler>(this, &MyObject::OnSuspending);
So I guess we’re stuck with the function overload with type deduction from the future.
But really, I think the winning move is not to play.
Instead of trying to make Callback fancy, create a separate function TypedEventHandlerCallback.
template< typename TCallbackObject, typename TArg1, typename TArg2> ComPtr<TypedEventHandler<TArg1, TArg2>> TypedEventHandlerCallback( TCallbackObject* object, HRESULT (TCallbackObject::*method)(TArg1, TArg2));
Then the original code would be
void MyClass::RegisterCompletion(ABI::IAsyncAction* action) { m_showingToken = inputPane->put_Showing( TypedEventCallback(this, &MyClass::OnInputPaneShowing).Get()); }
While we’re there, we can also make an EventHandlerCallback function.
template< typename TCallbackObject, typename TArg> ComPtr<EventHandler<TArg>> EventHandlerCallback( TCallbackObject* object, HRESULT (TCallbackObject::method)(IInspectable, TArg));
The post Could we use CTAD to simplify the use of WRL’s Callback function? appeared first on The Old New Thing.
From The Old New Thing via this RSS feed


