Andrea Bocco
Andrea Bocco

Reputation: 932

casting a returned iterator to const

I have the following for statement in my code:

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

The point is that the loop doesn't modify elements of the container, so it would make sense to declare iterators as const_iterator. I can easily solve the problem for the first using cbegin(), but the second is more complex. I can't declare cbegin() and cend() inside stable_partition, since of course stable_partition needs non const_iterators to do its work.

A possible solution is to replace auto with the proper type, which in this case was std::vector< std::string >::const_iterator. This forces the conversion from iterator to const_iterator on the second assignment.

Though, I don't like it. Types can easily and rapidly become unmanageable, so I'm looking for a solution that lets me use auto, without the need to declare something weird outside the loop. Any suggestion?

Upvotes: 4

Views: 718

Answers (2)

bolov
bolov

Reputation: 75698

The most clear solution in my opinion is to pull std::stable_partition before the for. This will result in an equivalent algorithm.

The problem is that stable_partition returns a iterator that can modify elements. Fortunately there is an implicit conversion from container::iterator to container::const_iterator (for most standard containers). To make the conversion you can specify the type of IterEnd with std::vector<T::const_iterator, or decltyp(Target.cbegin() or my personal preference:

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

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

For completeness you could keep all inside the for if you wish but it's less readable in my opinion:

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

Upvotes: 2

Richard Hodges
Richard Hodges

Reputation: 69864

Here's one way to express the idea through a functional interface:

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

expected output:

1
2
6
9

Upvotes: 0

Related Questions