Using perfect (and imperfect) forwarding to simplify C++ wrapper classes
source link: https://devblogs.microsoft.com/oldnewthing/20230104-00/?p=107668
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Using perfect (and imperfect) forwarding to simplify C++ wrapper classes
Raymond Chen
There may be cases where you have a C++ class that wants to wrap another C++ class that is contained as a member. In C++/WinRT, this can happen if your C++ class wants to act like an IVector
, but with some bonus methods or extra functionality.
// MIDL runtimeclass ItemCollection : [default] IVector<Item> { String ToJson(); }
The implementation of this will probably consist of an internal IVector
that forwards all of the PageCollection
‘s IVector
methods, plus one additional method for moving pages around.
// C++/WinRT namespace winrt::Contoso::implementation { struct ItemCollection : ItemCollectionT<ItemCollection> { Windows::Foundation::Collections::IIterator<Contoso::Item> First() { return m_items.First(); } Contoso::Item GetAt(uint32_t index) { return m_items.GetAt(index); } uint32_t Size() { return m_items.Size(); } bool IndexOf(Contoso::Item const& value, uint32_t& index) { return m_items.IndexOf(value, index); } uint32_t GetMany(uint32_t startIndex, array_view<Contoso::Item> items) { return m_items.GetMany(startIndex, items); } // And our bonus method hstring ToJson(); private: Windows::Foundation::Collections::IVector<Contoso::Item> m_items; }; }
It’s annoying that there’s so much boilerplate to do the method forwarding, and that we have to keep looking up the parameters and return types so that each forwarder has the correct signature. Fortunately, we can use perfect forwarding to write most of them for us:
namespace winrt::Contoso::implementation { struct ItemCollection : ItemCollectionT<ItemCollection> { template<typename...Args> decltype(auto) First(Args&&... args) { m_items.First(std::forward<Args>(args)...); } template<typename...Args> decltype(auto) GetAt(Args&&... args) { m_items.GetAt(std::forward<Args>(args)...); } template<typename...Args> decltype(auto) Size(Args&&... args) { m_items.Size(std::forward<Args>(args)...); } template<typename...Args> decltype(auto) IndexOf(Args&&... args) { m_items.IndexOf(std::forward<Args>(args)...); } template<typename...Args> decltype(auto) GetMany(Args&&... args) { m_items.GetMany(std::forward<Args>(args)...); } // And our bonus method hstring ToJson(); private: Windows::Foundation::Collections::IVector<Contoso::Item> m_items; }; }
Using perfect forwarding means that we don’t have to remember the types and number of parameters for each of the methods, or what the methods return. Furthermore, if there are multiple overloads of a method, a single perfect forwarder covers them all!
template<typename...Args> decltype(auto) Name(Args&& args) { m_widget.Name(std::forward<Args>(args)...); }
This forwarder forwards both the property setter and getter to the m_widget
.
We can take some shortcuts here, because we know that the parameters to C++/WinRT Windows Runtime methods are safe to pass through as lvalues, so we can get rid of the std::forward
. (Indeed, our original wrapper methods didn’t try to preserve rvalue-ness.)
We also know that the return value from C++/WinRT Windows Runtime methods are not references, so we can simplify the decltype(auto)
to auto
. This gives us
namespace winrt::Contoso::implementation { struct ItemCollection : ItemCollectionT<ItemCollection> { template<typename...Args> auto First(Args&&... args) { m_items.First(args...); } template<typename...Args> auto GetAt(Args&&... args) { m_items.GetAt(args...); } template<typename...Args> auto Size(Args&&... args) { m_items.Size(args...); } template<typename...Args> auto IndexOf(Args&&... args) { m_items.IndexOf(args...); } template<typename...Args> auto GetMany(Args&&... args) { m_items.GetMany(args...); } // And our bonus method hstring ToJson(); private: Windows::Foundation::Collections::IVector<Contoso::Item> m_items; }; }
Finally, we can take advantage of C++20 abbreviated function templates, which now makes the forwarder small enough to fit on one line:
namespace winrt::Contoso::implementation { struct ItemCollection : ItemCollectionT<ItemCollection> { auto First(auto&&... args) { m_items.First(args...); } auto GetAt(auto&&... args) { m_items.GetAt(args...); } auto Size(auto&&... args) { m_items.Size(args...); } auto IndexOf(auto&&... args) { m_items.IndexOf(args...); } auto GetMany(auto&&... args) { m_items.GetMany(args...); } // And our bonus method hstring ToJson(); private: Windows::Foundation::Collections::IVector<Contoso::Item> m_items; }; }
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK