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, SuspendingÂEventÂHandler), 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 TypedÂEventÂHandler 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));
0 comments
Be the first to start the discussion.