Last time, we created a helper function for creating a delegate from a weak pointer and a lambda. The weak pointer in question was a C++/WinRT weak pointer, but maybe you want to use this from a traditional C++ class, in which case what you have is not a winrt::weak_ref
but rather a std::weak_ptr
. Let’s adapt our function to work with std::weak_ptr
too.
Recall that we left off last time with this function:
template<typename T, typename Handler> auto weak_delegate( winrt::weak_ref<T> weak, Handler&& handler) { return [weak = std::move(weak), handler = std::forward<Handler>(handler)] (auto&&... args) mutable { if (auto strong = weak.get()) { handler(std::forward<decltype(args)>(args)...); } }; }
The only difference that matters (for our purposes) between winrt::weak_ref
and std::weak_ptr
is that winrt::weak_ref
uses the get()
method to promote the weak reference to a strong reference, whereas the corresponding method on std::weak_ptr
is called lock()
.
What we want to do is something weird like
template<typename Weak, typename Handler>
auto weak_delegate(
Weak&& weak,
Handler&& handler)
{
return [weak = std::forward<Weak>(weak),
handler = std::forward<Handler>(handler)]
(auto&&... args) mutable
{
if constexpr (is_specialization_v<Weak, std::weak_ptr>) {
auto strong = weak.lock();
} else {
auto strong = weak.get();
}
if (strong) {
handler(std::forward<decltype(args)>(args)...);
}
};
}
But that doesn’t work because the scope of strong
is wrong.¹
What we could do is turn it into a lambda:
template<typename Weak, typename Handler> auto weak_delegate( Weak&& weak, Handler&& handler) { return [weak = std::forward<Weak>(weak), handler = std::forward<Handler>(handler)] (auto&&... args) mutable { auto strong = [&] { if constexpr (is_specialization_v<Weak, std::weak_ptr>) { return weak.lock(); } else { return weak.get(); } }(); if (strong) { handler(std::forward<decltype(args)>(args)...); } }; }
Now, writing that is_specialization_v
thingie is going to be a bit of an annoyance, but it turns out we don’t need to do it. We can let function overloading do the work.
template<typename... Extra>
auto try_resolve_weak_pointer_to_strong(std::weak_ptr<Extra...> const& weak)
{
return weak.lock();
}
template<typename... Extra>
auto try_resolve_weak_pointer_to_strong(winrt::weak_ref<Extra...> const& weak)
{
return weak.get();
}
template<typename Weak, typename Handler>
auto weak_delegate(
Weak&& weak,
Handler&& handler)
{
return [weak = std::forward<Weak>(weak),
handler = std::forward<Handler>(handler)]
(auto&&... args) mutable
{
if (auto strong = try_resolve_weak_pointer_to_strong(weak)) {
handler(std::forward<decltype(args)>(args)...);
}
};
}
The try_
functions use a trick we looked at a little while ago.
Now you can use weak_
with C++ standard library weak pointers, as well as C++/WinRT weak references.
struct MyClass : std::enable_shared_from_this<MyClass> { ... void RegisterSomething(int otherData) { widget.Something(weak_delegate(weak_from_this(), [this, otherData] (auto&& sender, auto&& args) { DoThing1(sender); DoThing2(args); DoThing3(otherData); })); } };
A benefit of delegating the “resolve a weak pointer to a strong pointer” decision to a function overload is that you can extend support to other weak pointer libraries by adding new overloads.
// WRL WeakRef auto try_resolve_weak_pointer_to_strong(::Microsoft::WRL::WeakRef const& weak) { ::Microsoft::WRL::ComPtr<IInspectable> o; weak.As(&o); return o; } // wil com_weak_ptr template<typename... Extra> auto try_resolve_weak_pointer_to_strong( wil::com_ptr<IWeakReference, Extra...> const& weak) { return weak.try_copy<IUnknown>(); } // Unreal Engine TWeakPtr template<typename... Extra> auto try_resolve_weak_pointer_to_strong(TWeakPtr<Extra...> const& weak) { return weak.Pin(); } // Unreal Engine TWeakObjectPtr template<typename... Extra> auto try_resolve_weak_pointer_to_strong(TWeakObjectPtr<Extra...> const& weak) { return weak.Get(); } // Qt QWeakPointer template<typename... Extra> auto try_resolve_weak_pointer_to_strong(QWeakPointer<Extra...> const& weak) { return weak.toStrongRef(); }
Note that we are using the Extra...
technique for accepting any specialization of a C++ template type.²
The case of wil
is a little tricky because com_weak_ptr
is itself a template alias. We have to use the name of the base template type rather than the type alias.
¹ If you decide to deepen the sad history of Visual Studios custom __if_exists
keyword, you could write
template<typename Weak, typename Handler> auto weak_delegate( Weak&& weak, Handler&& handler) { return [weak = std::forward<Weak>(weak), handler = std::forward<Handler>(handler)] (auto&&... args) mutable { __if_exists(Weak::get) { auto strong = weak.get(); } __if_exists(Weak::lock) { auto strong = weak.lock(); } if (strong) { handler(std::forward<decltype(args)>(args)...); } }; }
But please, don’t do that. Use SFINAE instead.
² Others have noted that the Extra...
trick doesn’t work for templates that take non-type template parameters or other templates, so maybe SFINAE is the way to go after all.
// winrt weak_ref template<typename T> auto try_resolve_weak_pointer_to_strong(T const& weak) -> decltype(weak.get()) { return weak.get(); } // std weak_ptr template<typename T> auto try_resolve_weak_pointer_to_strong(T const& weak) -> decltype(weak.lock()) { return weak.lock(); } // WRL WeakRef (no change) auto try_resolve_weak_pointer_to_strong(::Microsoft::WRL::WeakRef const& weak) { ::Microsoft::WRL::ComPtr<IInspectable> o; weak.As(&o); return o; } // wil com_weak_ptr template<typename T> auto try_resolve_weak_pointer_to_strong(T const& weak) -> decltype(weak.try_copy<IUnknown>()) { return weak.try_copy<IUnknown>(); } // Unreal Engine TWeakPtr template<typename T> auto try_resolve_weak_pointer_to_strong(T const& weak) -> decltype(weak.Pin()) { return weak.Pin(); } // Unreal Engine TWeakObjectPtr // (Would also accidentally match WRL if not for the better overload above) template<typename T> auto try_resolve_weak_pointer_to_strong(T const& weak) -> decltype(weak.Get()) { return weak.Get(); } // Qt QWeakPointer template<typename T> auto try_resolve_weak_pointer_to_strong(T const& weak) -> decltype(weak.toStrongRef()) { return weak.toStrongRef(); }
One of the risks of using SFINAE for duck typing is that you may detect things that you didn’t mean to. For example, here, we have a name collision: Both WRL WeakRef and Unreal Engine TWeakObjectPtr have a Get
method, but they do different things. The WRL Get
method returns the wrapped IWeakReference*
(no resolving happens), whereas the Unreal Engine Get
method resolves the weak pointer to a strong pointer. We have to make sure that the WRL specialization gets picked up for WRL WeakRef.
0 comments