简体   繁体   中英

Implementing a C++ range-for wrapper for lvalues and rvalues

I've implemented a little helper wrapper for range-for loops that allows iterating over both keys and values of the associative Qt containers like QMap and QHash , extracting each pair as a structured binding, eg:

const QMap<int, QString> digitMap = { {1, "one"}, {2, "two"}, {3, "three"} };
for (auto [intKey, strVal] : make_keyval(digitMap)) {
    qDebug() << intKey << "->" << strVal;
}

This isn't supported out-of-the-box for the Qt containers as they require using a specific pair of constKeyValueBegin() and constKeyValueEnd() methods (let's assume only non-mutating iterations). So my idea was to write a simple wrapper type that provides a pair of regular begin() and end() methods which simply call the keyValue ones on the container.

That's easy enough, but as a stretch goal I also wanted to make the wrapper usable with temporaries, like make_keyval(sometype.toMap()) . The main challenge there was extending the lifetime of the temporary through the end of the iteration, as I'm going through a proxy object. Here's the solution I came up with:

template<typename C>
struct key_value_range_iterator {
    key_value_range_iterator(const C& container) : m_rvalueContainer(nullptr), m_containerRef(container) {}
    key_value_range_iterator(const C&& container) : m_rvalueContainer(std::make_unique<C>(std::move(container))), m_containerRef(*m_rvalueContainer) {}

    typename C::const_key_value_iterator begin() const { return m_containerRef.constKeyValueBegin(); }
    typename C::const_key_value_iterator end() const { return m_containerRef.constKeyValueEnd(); }

private:
    const std::unique_ptr<C> m_rvalueContainer;
    const C& m_containerRef;
};

template<typename C>
auto make_keyval(C&& container) { return key_value_range_iterator(std::forward<C>(container)); }

This seems to work fine for both regular variables and temporaries. For temporaries, m_rvalueContainer is used to store the moved temporary for the duration of the iteration, then it's referenced by m_containerRef . In the regular variable case, we just store the lvalue reference in m_containerRef directly leaving m_rvalueContainer unset. I verified the right constructor gets called in each case, and that the temporary only gets destroyed after the range-for loop completes.

So my question is simply: is this a correct implementation for my wrapper, or did I miss something? Or maybe there's a corner case I haven't thought of?

Note that in my initial version, I had m_rvalueContainer as a regular value, but I figured this would end up instantiating an empty container for nothing in the lvalue case (though that's a cheap operation for Qt containers), so I replaced it with unique_ptr to ensure there's no overhead in that case. Yes, it still ends up initializing it to nullptr, but that's neglectable.

Any other comments or recommendations?

Since make_keyval knows if the object passed in is lvalue or rvalue, you can pass that parameter on to your wrapper.

#include <type_traits>

template<typename C>
struct key_value_range_iterator {
    key_value_range_iterator(const C container) : m_containerRef(container) {}

    using iterator = typename std::remove_reference_t<C>::const_key_value_iterator;

    iterator begin() const { return m_containerRef.constKeyValueBegin(); }
    iterator end() const { return m_containerRef.constKeyValueEnd(); }

private:
    const C m_containerRef;
};

template<typename C>
auto make_keyval(C&& container) { return key_value_range_iterator<C>(std::forward<C>(container)); }

When passing an lvalue C is deduces as QMap& making the wrapper hold a reference. When passing an rvalue C is deduces as QMap making the wrapper move the rvalue into it's member.

Since C can be QMap& we need to use std::remove_reference to obtain the iterator type succesfully for the lvalue case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM