tags:

views:

885

answers:

8

I like STL algorithms, and prefer use algorithms rather than usual loops.
Almost all STL algorithms are usually used as:

std::algorithm_name( container.begin(), container.end(), ..... )

container.begin(), container.end() - is one of most popular words pair in my projects.

Does anybody have the same problem?
How do you Guys solve this problem?
What could you propose to avoid this duplication? I see a few ways for solution, but all of them have different limitations (macro usage, not compatible with usual pointers, etc.).

+19  A: 

Many have encountered this nuisance. Though the iterator concept is exceptionally general, it lacks some usability.

Enter the 'range' concept. It is to be preferred to avoid any code duplication. So if you encounter .begin() and .end() pairs all over the code, it's good practice to create a layer in between the 'iterator-getting' and the actual algorithms.

References:

...

xtofl
+1. There's also Boost.Range: http://www.boost.org/doc/libs/1_38_0/libs/range/index.html
j_random_hacker
Nice. Thank you, @xtofl, @j_random_hacker. I didn't see this libs, earlier.
bb
+23  A: 

The next C++ standard, C++0X (where X stands for, hopefully, 9) will add the possibility to change from iterator perspective to container perspective. You will be able to do eg.

std::sort(my_vec);

If you cant wait for this I would recommend you to look at: Boost.Range

And if you are a really interested in iterators/ranges I would recommend you to read Andrei's "iterators must go"

Schildmeijer
I knew. But thank you for reminding.
bb
+3  A: 

C++ 0x is addressing this minor annoyance within the language.

Pontus Gagge
I use not just for_each algorithm. But anyway, thank you, it will helpfull.
bb
+8  A: 
#define ALL(x) (x).begin(), (x).end()

sort(ALL(vec));
'cos macros which evaluate their parameters multiple times are always entertaining ;-)
Steve Jessop
+5  A: 

First, I don't think it's a big issue. In general, I don't really care about typing a few more characters. readability is more important, and I think begin/end is perfectly readable.

Shorter container names can help though (con.begin() is easier to type than container.begin())

And passing iterators around instead of the container itself means that you don't have to call begin/end more than once in any case.

It's not really something that bothers me.

jalf
I don't care about typing additional symbols in variable/function/class/.. name, but I'm trying to avoid duplications.
bb
Why? What's the difference? Typing a class name twice is just as much duplication as typing begin() twice, isn't it?
jalf
Long names improving readability, but in case with begin()/end() we have code duplication. Why I should pass container.begin(), container.end() if I want apply algorithm for all contained items?
bb
Jalf, you are not against typing begin and end but recommend shortened variable names, right?
Mykola Golubyev
I'm not for or against anything. :pAnd long names can be a pain to read. They definitely do not always improve readability. I'm simply pointing out I don't think it's a big problem in either case. But there are small simple tricks you can use to shorten it a bit without hurting readability. :)
jalf
+3  A: 

If it got really bad, I would probably create a bunch of templates for my most commonly used algorithms in a new namespace:

namespace my_ranged_algorithms {
    // Metafunction for extracting an appropriate iterator from
    // the container type
    template <typename T>
    struct get_iterator_type_for;

    // For vectors
    template <typename T>
    struct get_iterator_type_for<std::vector<T> > {
        typedef typename std::vector<T>::iterator type;
    };

    template <typename T>
    struct get_iterator_type_for<std::vector<T> const> {
        typedef typename std::vector<T>::const_iterator type;
    };

    // For C arrays
    template <typename T, size_t N>
    struct get_iterator_type_for<T(&)[N]> {
        typedef T* type;
    };

    // Generic begin() and end() wrappers

    // For all standard containers        
    template <typename Cont>
    typename get_iterator_type_for<Cont>::type begin(Cont& c) {
        return c.begin();
    }

    template <typename Cont>
    typename get_iterator_type_for<Cont>::type end(Cont& c) {
        return c.end();
    }

    // For C arrays
    template <typename T, size_t N>
    typename get_iterator_type_for<T (&)[N]>::type begin(T (&c)[N]) {
        return c;
    }

    template <typename T, size_t N>
    typename get_iterator_type_for<T (&)[N]>::type end(T (&c)[N]) {
        return c + N;
    }

    // Finally, the actual algorithm wrappers

    // copy
    template <typename Cont, typename OutIter>
    OutIter copy(Cont& from, OutIter to) {
        return std::copy(begin(from), end(from), to);
    }

    // remove
    template <typename Cont, typename T>
    typename get_iterator_type_for<Cont>::type remove(Cont& from, T x) {
        return std::remove(begin(from), end(from), x);
    }

    // etc.
};

Then call them like so:

vector<int> a, b;
using namespace my_ranged_algorithms;

copy(a, back_inserter(b));
b.erase(remove(b, 42), b.end()); // Remember to call erase() after remove()!
j_random_hacker
+1  A: 

boost::range_ex will solve this before c++0x.

And it's not hard to write a few wrappers yourself in the meantime.

Marcus Lindblom
+2  A: 

This nice presentation [PDF] about a possible future solution to this was recently linked from reddit. it discusses how to fully replace iterators with the range concept.

shoosh
Thanks, nice article. I was filling all about what Andrei write in this presentation, but couldn't organize my minds and formulate all what I want. Range concept is very good direction.
bb