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