3

Using perfect (and imperfect) forwarding to simplify C++ wrapper classes

 1 year ago
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.
neoserver,ios ssh client

Using perfect (and imperfect) forwarding to simplify C++ wrapper classes

RaymondChen_5in-150x150.jpg

Raymond Chen

January 4th, 20232 0

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 Page­Collection‘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;
    };
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK