Why should I always specify the range in the functions of the STL algorithm explicitly, even if I want to work with the entire container?

When using STL functions such as sort() or min_element() , I always need to tell the range to start and end explicitly:

 void range_example() { std::vector<int> list = {7, 3, 9, 1, 5, 2}; auto found_element = std::min_element(list.begin(), list.end()); std::cout << *found_element << std::endl; } 

This makes sense if I intend to work only on parts of my container, but more often I need functions to work with the entire container. Is there a reason why there is no overloaded function that allows this:

 std::vector<int> list = {7, 3, 9, 1, 5, 2}; auto found_element = std::min_element(list); 

Is there a way to make a function call for the entire range of the container that I missed?

EDIT: I know that I can encapsulate this in a function on my own, but since this should be done for all functions, I would like to avoid this if there is a better way.

+47
c ++ algorithm c ++ 11 stl
Aug 19 '15 at 7:49
source share
6 answers

In most cases, the standard library is designed to provide the minimum interface required to perform all the required tasks, i.e. & thinsp; he is trying to avoid swelling the interface. You can work with the entire container when the algorithm accepts a pair of iterators, but you cannot work on a subrange if the algorithm accepts the container. Thus, a pair of iterators is more fundamental, and so that provides a standard library. Convenience features are usually not included.

However, you, of course, are not the first person to think so, and there is the whole Boost.Range library dedicated to handling a range (both a container and an arbitrary range) as a single object instead of a pair of iterators.

There is also a formal proposal to include the Eric Niebler range library in a future version of the C ++ standard library.

+42
Aug 19 '15 at 7:55
source share
β€” -

This is because STL algorithms are container independent. Iterators provide a single way of working, with the only limitation being what guarantees this algorithm requires from these iterators.

For example, if you want to perform a linear search for min_element() , you only need forward iterators (i.e. they must support operator++ ). Thus, you can write one simple template implementation that will work with almost every container, regardless of how the container is implemented under the hood.

You can overload functions to accept only the container and apply begin() and end() tags to them, but this will mean that you have another interface to remember.

Edit

I assume that there are several other arguments that could be made. Since STL was associated with mathematical beauty and the emphasis that algorithms are separate from containers, always passing iterators would reinforce this concept.

On the other hand, in terms of the C ++ language as a whole, one of Stroustrup's main goals was to educate developers. The full power of STL algorithms comes from the ability to skip arbitrary ranges of iterators, but most of the time you want to work on the entire container. If you provided overloads for the entire container, you could argue that a large number of people will never want to learn to use range versions, because it is these versions that fall into the category of "another interface to remember."

+7
Aug 19 '15 at 7:51
source share

The practical reason for overloading containers or ranges has not yet been completed is related to the proposal of concepts.

Algorithms now take a bunch of template parameters and set requirements on them. If you pass types that do not meet the requirements, they may not compile or may just not work properly.

Overloads almost always simply include a different number of parameters.

If we add container / range overloading, we need to either give them new names (ick) or modify existing algorithms to be overloaded. Overloading (iterator, iterator, value) and overloading (range, value, function) have the same number of arguments, and one of them can easily confuse the compiler (and unexpected results may occur).

While we could go and specify overload restrictions for all existing algorithms one by one, and then add overloads for ranges, at the moment the code and requirements will be ugly. After adding concepts to the language, we hopefully will have a set of brief concepts that describe what parameters should be, and a language function that makes the implementation easy and clean.

It may turn out that these algorithms may not be practically overloads of existing algorithms due to compatibility reasons or what you have, but even this will be easier to work with.

Initially, iterators were enough, and they separated the containers from the algorithms. Then ranges could be added, but the language mechanism for interpreting containers with a clean range was somewhat lacking (for example, the decltype type), and this was not strictly required. Since then, support for the range has been desirable, but not easy to do, and there is (on the horizon) an extension of the language that will make it much cleaner and easier.

+6
Aug 19 '15 at 14:20
source share

You can implement your own:

 template<class Container> typename Container::iterator min_element(Container& c) { using std::begin; using std::end; return std::min_element(begin(c), end(c)); } std::vector<int> list = {7, 3, 9, 1, 5, 2}; auto found_element = min_element(list); 

For completeness:

 template<class Container> typename std::conditional< std::is_const<Container>::value, typename Container::const_iterator, typename Container::iterator>::type min_element(Container& c) { using std::begin; using std::end; return std::min_element(begin(c), end(c)); } 

and support arrays:

 template<typename T, size_t N> T* min_element(T (&arr)[N]) { return std::min_element(arr, arr + N); } 
+4
Aug 19 '15 at 7:57
source share

This is one of the moments when I think that using macros is good. Just make sure that evaluating the expression inside the macro has no side effects.

 #include <boost/preprocessor/punctuation/comma.hpp> // this is just: #define BOOST_PP_COMMA() , #define RANGE_ARGS( container ) container.begin ( ) BOOST_PP_COMMA() container.end ( ) #define RANGE_ARGS_C( container ) container.cbegin ( ) BOOST_PP_COMMA() container.cend ( ) #define RANGE_ARGS_R( container ) container.rbegin ( ) BOOST_PP_COMMA() container.rend ( ) #define RANGE_ARGS_CR( container ) container.crbegin ( ) BOOST_PP_COMMA() container.crend ( ) 

This gives in your case:

 std::vector<int> list = {7, 3, 9, 1, 5, 2}; auto const found_element = std::min_element( RANGE_ARGS_C(list) ); 
0
Sep 08 '15 at 19:38
source share

It is easy to define such a function yourself. for example

 #include <iostream> #include <vector> #include <algorithm> #include <iterator> template <class T> decltype( auto ) min_element( T &c ) { return std::min_element( std::begin( c ), std::end( c ) ); } int main() { int a[] = { 5, 7, 3, 1, 9, 6 }; std::cout << *min_element( a ) << std::endl; std::vector<int> v( std::begin( a ), std::end( a ) ); std::cout << *min_element( v ) << std::endl; } 

Program exit

 1 1 

I made such a proposal for the std::sort and std::reverse algorithms. You can read about it in my personal forum, which I support, for example, in my travel website. here

Although it is written in Russian, you can translate it, for example, using Bing or google.

-one
Aug 19 '15 at 8:11
source share



All Articles