Приведение возвращенного итератора к const

У меня есть следующее for утверждение в моем коде:

for (auto Iter = Target.begin(), 
        IterEnd = std::stable_partition(Target.begin(), Target.end(), Check);
    Iter != IterEnd; ++Iter)
    { /* loop statement */ }

Дело в том, что цикл не изменяет элементы контейнера, поэтому имеет смысл объявить итераторы как const_iterator, Я легко могу решить проблему при первом использовании cbegin(), но второй сложнее. Я не могу объявить cbegin() а также cend() внутри stable_partitionконечно stable_partition потребности non const_iterators делать свою работу.

Возможное решение - заменить auto на соответствующий тип, который в данном случае был std::vector< std::string >::const_iterator, Это заставляет преобразование из iterator в const_iterator на втором задании.

Хотя мне это не нравится. Типы могут легко и быстро стать неуправляемыми, поэтому я ищу решение, которое позволило бы мне использовать auto без необходимости объявлять что-то странное вне цикла. Любое предложение?

2 ответа

Самое понятное решение на мой взгляд это тянуть std::stable_partition перед for, Это приведет к эквивалентному алгоритму.

Проблема в том, что stable_partition возвращает итератор, который может изменять элементы. К счастью, есть неявное преобразование из container::iterator в container::const_iterator (для большинства стандартных контейнеров). Чтобы сделать преобразование, вы можете указать тип IterEnd с std::vector<T::const_iterator, или же decltyp(Target.cbegin() или мои личные предпочтения:

auto Iter = Target.cbegin();
decltype(Iter) IterEnd = std::stable_partition(Target.begin(), Target.end(), Check);

for (; Iter != IterEnd; ++Iter)
{
}

Для полноты вы можете держать все внутри for если хотите, но на мой взгляд это менее читабельно:

for (auto Iter = Target.cbegin(),
        IterEnd = (decltype(Iter)) std::stable_partition(Target.begin(), Target.end(), Check);
     Iter != IterEnd;
     ++Iter)
{}

Вот один из способов выразить идею через функциональный интерфейс:

#include <vector>
#include <algorithm>
#include <iostream>

namespace detail {
    template<class Container, class F>
    struct const_partitioned_target
    {
        using container_type = std::decay_t<Container>;
        using const_iterator = typename container_type::const_iterator;

        const_partitioned_target(Container& cont, F f)
            : first(cont.cbegin())
            , last(std::partition(cont.begin(), cont.end(), f))
        {

        }

        const_iterator begin() const { return first; }

        const_iterator end() const { return last; }

        const_iterator first, last;
    };
}

template<class Container, class F>
auto const_partitioned_target(Container& cont, F&& f)
{
    return detail::const_partitioned_target<Container, std::decay_t<F>>(cont, std::forward<F>(f));
};

int main()
{
    std::vector<int> Target { 1, 2, 6, 9, 10, 20, 30, 40 };

    auto Check = [](auto&& x)
    {
        return x < 10;
    };

    for(auto&& elem : const_partitioned_target(Target, Check))
    {
        // elem will have the type: int const&
        std::cout << elem << '\n';
    }
}

ожидаемый результат:

1
2
6
9

Не то чтобы я думаю, что это хорошая идея, чтобы всегда пихать инициализацию в for предложение, вы можете использовать структурированные привязки

for (auto [IterEnd, Iter] =
         std::pair{std::stable_partition(Target.begin(), Target.end(), Check),
                   Target.begin()};
     Iter != IterEnd; ++Iter) { /* loop statement */
}
Другие вопросы по тегам