Реализация оболочки диапазона C ++ для lvalues ​​и rvalues - PullRequest
1 голос
/ 11 июля 2020

Я реализовал небольшую вспомогательную оболочку для циклов range-for, которая позволяет перебирать как ключи, так и значения ассоциативных контейнеров Qt, таких как QMap и QHash, извлекая каждую пару как структурированную привязку, например:

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

Стандартно это не поддерживается для контейнеров Qt, так как они требуют использования специальной пары c методов constKeyValueBegin() и constKeyValueEnd() (предположим, что только немутантные итерации ). Итак, моя идея заключалась в том, чтобы написать простой тип-оболочку, который предоставляет пару обычных методов begin() и end(), которые просто вызывают методы keyValue в контейнере.

Это достаточно просто, но в качестве растянутой цели я также хотел сделать оболочку доступной для временных файлов, например make_keyval(sometype.toMap()). Основная проблема заключалась в том, чтобы продлить время жизни временного объекта до конца итерации, когда я просматриваю прокси-объект. Вот решение, которое я придумал:

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)); }

Кажется, это нормально работает как для обычных переменных, так и для временных. Для временных файлов m_rvalueContainer используется для хранения перемещенного временного объекта на время итерации, затем на него ссылается m_containerRef. В случае с обычной переменной мы просто сохраняем ссылку lvalue в m_containerRef, оставляя m_rvalueContainer неустановленным. Я проверил, что в каждом случае вызывается правильный конструктор и что временное уничтожается только после завершения range-for l oop.

Итак, мой вопрос прост: это правильная реализация для моей оболочки, или я что-то упустил? Или, может быть, есть угловой случай, о котором я не подумал? случай lvalue (хотя это дешевая операция для контейнеров Qt), поэтому я заменил его на unique_ptr, чтобы гарантировать отсутствие накладных расходов в этом случае. Да, он все равно инициализирует его значением nullptr, но этим можно пренебречь.

Есть другие комментарии или рекомендации?

1 Ответ

3 голосов
/ 11 июля 2020

Поскольку make_keyval знает, является ли переданный объект lvalue или rvalue, вы можете передать этот параметр своей оболочке.

#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)); }

При передаче lvalue C выводится как QMap& сделать обертку ссылкой. При передаче rvalue C выводится как QMap, заставляя оболочку перемещать rvalue в свой член.

Поскольку C может быть QMap&, нам нужно использовать std::remove_reference для получения типа итератора успешно для случая lvalue.

...