Creating a COM pointer that supports range-based iteration

Iterating over certain COM collection objects can be cumbersome, so I’m trying to create some COM pointers that support range-based iteration. They’re derived from CComPtr. For example, here’s an IShellItemArray pointer that I came up with that allows range-based iteration over its IShellItems (so you can iterate over it by just doing for (const auto psi : psia) ):

class CShellItemArrayPtr : public CComPtr<IShellItemArray>
{
public:
    using CComPtr::CComPtr;

private:
    class CIterator
    {
    public:
        CIterator(IShellItemArray* psia) : m_hr(S_FALSE)
        {
            HRESULT hr;
            hr = psia->EnumItems(&m_pesi);
            if (SUCCEEDED(hr))
                ++*this;
        }

        const CIterator& operator++ ()
        {
            m_psi.Release();
            m_hr = m_pesi->Next(1, &m_psi, NULL);
            return *this;
        }

        BOOL operator!= (const HRESULT hr) const
        {
            return m_hr != hr;
        }

        IShellItem* operator* ()
        {
            return m_psi;
        }

    private:
        CComPtr<IShellItem> m_psi;
        CComPtr<IEnumShellItems> m_pesi;
        HRESULT m_hr;
    };

public:
    CIterator begin() const
    {
        return CIterator(p);
    }

    HRESULT end() const
    {
        return S_FALSE;
    }
};

Similarly, here’s an IShellWindows pointer I came up with that allows range-based iteration over its individual IWebBrowser2s:

class CShellWindowsPtr : public CComPtr<IShellWindows>
{
public:
    using CComPtr::CComPtr;

private:
    class CIterator
    {
    public:
        CIterator(IShellWindows* psw) : m_hr(S_FALSE)
        {
            HRESULT hr;
            CComPtr<IUnknown> punk;
            hr = psw->_NewEnum(&punk);
            if (SUCCEEDED(hr))
            {
                hr = punk->QueryInterface(&m_pev);
                if (SUCCEEDED(hr))
                    ++*this;
            }
        }

        const CIterator& operator++ ()
        {
            m_pwb2.Release();
            CComVariant var;
            m_hr = m_pev->Next(1, &var, NULL);
            if (m_hr == S_OK)
                var.pdispVal->QueryInterface(&m_pwb2);
            return *this;
        }

        BOOL operator!= (const HRESULT hr) const
        {
            return m_hr != hr;
        }

        IWebBrowser2* operator* () const
        {
            return m_pwb2;
        }

        CComPtr<IWebBrowser2> m_pwb2;
        CComPtr<IEnumVARIANT> m_pev;
        HRESULT m_hr;
    };

public:
    CIterator begin() const
    {
        return CIterator(p);
    }

    HRESULT end() const
    {
        return S_FALSE;
    }

};

My question is whether there’s a smart way of abstracting out this iteration behavior into a more generalized (likely templated) class. I’m not really sure how to go about it, or if it’s practically possible. Thank you for any input.

Answer

All IEnum... interfaces have a common design, even though they output different element types. That design can lend itself to C++ templates, so I would suggest separating out CIterator into a standalone template class that can iterate any IEnum... interface, and then have CShellWindowsPtr and CShellItemArrayPtr make use of that class. For example

void CEnumRelease(CComVariant &value)
{
    value.Clear();
}

template <typename IntfType>
void CEnumRelease(CComPtr<IntfType> &value)
{
    value.Release();
}

// other overloads as needed...

template <typename Intf>
void CEnumTransform(CComVariant &src, CComPtr<Intf> &dest)
{
    if (src.vt & VT_TYPEMASK) == VT_UNKNOWN) {
        IUnknown *punk = (src.vt & VT_BYREF) ? *(src.ppunkVal) : src.punkVal;
        if (punk) punk->QueryInterface(IID_PPV_ARGS(&dest));
    }
    else if ((src.vt & VT_TYPEMASK) == VT_DISPATCH) {
        IDispatch *pdisp = (src.vt & VT_BYREF) ? *(src.ppdispVal) : src.pdispVal;
        if (pdisp) pdisp->QueryInterface(IID_PPV_ARGS(&dest));
    }
}

template <typename SrcIntf, typename DestIntf>
void CEnumTransform(CComPtr<SrcIntf> &src, CComPtr<DestIntf> &dest)
{
    if (src) src->QueryInterface(IID_PPV_ARGS(&dest));
}

// other overloads as needed...

#include <type_traits>

template<typename IEnumType, typename ElementType, typename IntermediateType = ElementType>
class CEnumIterator
{
public:
    CEnumIterator() : m_enum()
    {
    }

    CEnumIterator(CComPtr<IEnumType> &enum) : m_enum(enum)
    {
        ++*this;
    }

    CEnumIterator& operator++ ()
    {
        CEnumRelease(m_currentValue);
        if (m_enum) {
            if constexpr (!std::is_same_v<IntermediateType, ElementType>) {
                IntermediateType tmp;
                if (m_enum->Next(1, &tmp, NULL) != S_OK)
                    m_enum.Release();
                else
                    CEnumTransform(tmp, m_currentValue);
            }
            else {
                if (m_enum->Next(1, &m_currentValue, NULL) != S_OK) {
                    m_enum.Release();
            }
        }
        return *this;
    }

    bool operator == (const CEnumIterator &rhs) const
    {
        return m_enum == rhs.m_enum;
    }

    bool operator != (const CEnumIterator &rhs) const
    {
        return m_enum != rhs.m_enum;
    }

    ElementType& operator* ()
    {
        return m_currentValue;
    }

private:
    CComPtr<IEnumType> m_enum;
    ElementType m_currentValue;
};

class CShellItemArrayPtr : public CComPtr<IShellItemArray>
{
public:
    auto begin() const
    {
        CComPtr<IEnumShellItems> enum;
        if (p) p->EnumItems(&enum);
        return CEnumIterator<IEnumShellItems, CComPtr<IShellItem>>(enum);
    }

    auto end() const
    {
        return CEnumIterator<IEnumShellItems, CComPtr<IShellItem>>();
    }
};

class CShellWindowsPtr : public CComPtr<IShellWindows>
{
public:
    auto begin() const
    {
        CComPtr<IEnumVARIANT> enum;
        if (p) {
            CComPtr<IUnknown> punk;
            if (SUCCEEDED(p->_NewEnum(&punk) && punk)
                punk->QueryInterface(IID_PPV_ARGS(&enum));
        }
        return CEnumIterator<IEnumVARIANT, CComPtr<IWebBrowser2>, CComVariant>(enum);
    }

    auto end() const
    {
        return CEnumIterator<IEnumVARIANT, CComPtr<IWebBrowser2>, CComVariant>();
    }
};