Skip to content

Instantly share code, notes, and snippets.

@jonwis
Created November 19, 2023 07:15
Show Gist options
  • Save jonwis/1354089c149f30e609d91e4156b5641e to your computer and use it in GitHub Desktop.
Save jonwis/1354089c149f30e609d91e4156b5641e to your computer and use it in GitHub Desktop.
Yet another enum iterator attempt
#include <wil/com.h>
// Wraps a type like IEnumIUknown and exposes it as a forward iterator,
// or like IEnumIDList and exposes it as a forward iterator of type TStoredType.
// The IEnumType must have the following methods:
// HRESULT Next(ULONG celt, T* rgelt, ULONG* pceltFetched)
template <typename IEnumType, typename TStoredType>
struct iterator
{
wil::com_ptr<IEnumType> m_enum{};
uint32_t m_offset{0};
bool m_isEnd{false};
TStoredType m_currentValue{};
struct end_tag_t{ };
iterator(iterator&&) = default;
iterator(iterator const&) = delete;
iterator& operator=(iterator&&) = default;
iterator& operator=(iterator const&) = delete;
iterator(IEnumType* enumPtr) : m_enum(enumPtr)
{
FetchNext();
}
explicit iterator(end_tag_t) : m_isEnd(true)
{
}
auto operator->()
{
return wistd::addressof(m_currentValue);
}
auto& operator*()
{
return m_currentValue;
}
iterator& operator++()
{
// If we're already at the end, don't try to advance. Otherwise, use Next to advance.
if (!m_isEnd)
{
FetchNext();
}
return *this;
}
bool operator!=(iterator const& other) const
{
return !(*this == other);
}
bool operator==(iterator const& other) const
{
return (m_isEnd == other.m_isEnd) ||
(m_enum == other.m_enum && m_offset == other.m_offset);
}
private:
void FetchNext()
{
if (!m_isEnd)
{
m_currentValue = {};
auto hr = m_enum->Next(1, &m_currentValue, nullptr);
if (hr == S_FALSE)
{
m_isEnd = true;
}
else if (FAILED(hr))
{
throw winrt::hresult_error(hr, L"Failed to get next");
}
else
{
++m_offset;
}
}
}
};
auto make_range(IEnumUnknown* enumPtr)
{
using i = iterator<IEnumUnknown, wil::com_ptr<::IUnknown>>;
return std::make_pair<i, i>(enumPtr, i{i::end_tag_t{}});
}
@oldnewthing
Copy link

template<typename>
struct com_enumerator_next_traits;

template<typename Itf, typename T>
struct com_enumerator_next_traits<int32_t(Itf::*)(uint32_t, T*, uint32_t*)>
{
    using Interface = Itf;
    using Result = T;
};

template<typename Interface>
struct com_enumerator_traits
{
    using Result = typename com_enumerator_next_traits<decltype(&Interface::Next)>::Result;
    using com_result = std::conditional_t<std::is_base_of_v<::IUnknown, Result>, wil::com_ptr<Result>, Result>;
};

template <typename IEnumType, typename TStoredType = typename com_enumerator_traits<IEnumType>::com_result>
{
    /* as before */
};

Now you can create an iterator<IEnumXxx> and we will deduce TStoredType to be the thing that IEnumXxx::Next produces, and wrap it in a com_ptr if it's a COM interface. (If not, and there is cleanup necessary, then you must specify a unique_mystruct as the result type so the results aren't leaked.)

// SFINAE rejects anything that isn't a COM enumerator.
template<typename IEnumXxx, typename TStoredType = typename com_enumerator_traits<IEnumType>::smart_result>
auto make_range(IEnumXxx* enumerator)
{
    using i = iterator<IEnumXxx, TStoredType>;
    return std::make_pair<i, i>(enumPtr, i{i::end_tag_t{}});
}

We can get rid of is_end by setting m_enum = nullptr when we reach the end. The empty iterator then acts as the end iterator.

We can get rid of m_offset. Input iterators are not required to support meaningful == for non-end iterators, because applying ++ to an input iterator invalidates any copies of the unincremented iterator (so they are not legal to pass to ==).

bool operator==(iterator const& other) const { return m_isEnd == other.m_isEnd; }

// or - if we get rid of m_isEnd
bool operator==(iterator const& other) const { return m_enum.get() == other.m_enum.get(); }

@jonwis
Copy link
Author

jonwis commented Nov 21, 2023

Aw man, gists don't have reactions. Take this 🎉 instead.

@oldnewthing
Copy link

oldnewthing commented Nov 21, 2023

I was confused by the comment at the top "exposes it as a forward iterator of type TStoredType.", because the iterator<Interface, Result> is not a forward iterator, doesn't even try to be. It's not copyable or assignable. Really, it's an input iterator not a forward iterator.

@asklar
Copy link

asklar commented Nov 26, 2023

this is kind of what I had in the original PR: microsoft/wil#234
the problem is that we need a bit more traits for the output type for Next: e.g. PIDLs aren't COM, but should be wrapped in something like a wil::unique_cotaskmemptr to not be leaked. So perhaps something with a traits that can be specialized would work?

Or maybe I'm overthinking and this can be

wil::com_ptr<IEnumIDList> enumPidls;
folder>EnumObjects(..., &enumPidls);
for (auto pidl : enumPidls)
{
    // use the pidl
    ILFree(pidl);
}

thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment