Приведение возвращенного итератора к 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 */
}