grefab
grefab

Reputation: 2045

Why do I have to always specify the range in STL's algorithm functions explicitly, even if I want to work on the whole container?

When using STL's functions like sort() or min_element() I always have to specify the range by begin 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 part of my container, but more often I need the functions to work on the whole container. Is there a reason why there isn't an overloaded function that allows for this:

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

Is there a way to accomplish a function call for the total range of a container that I have overlooked?

EDIT: I'm aware that I can encapsulate that in a function myself, but because this must be done for all functions I'd like to avoid that if there is a better way.

Upvotes: 49

Views: 2209

Answers (6)

KevinZ
KevinZ

Reputation: 3299

This is one of the times where I think it is fine to use macros. Just make sure that computing 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 yields, in your case:

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

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275405

The practical reason why container or range overloads hasn't been done yet has to do with the concepts proposal.

Right now, the algorithms take a bunch of template parameters, and place requirements on them. If you pass types that don't match the requirements, they can fail to compile or just fail to work properly.

Overloads almost always simply involve a different number of parameters.

If we where to add container/range overloads, then we'd either have to give them new names (ick), or modify the existing algorithms to be overload-smart. A (iterator, iterator, value) overload and a (range, value, function) overload have the same number of arguments, and which one is being called could easily get confusing to the compiler (and unexpected results could occur).

While we could go and specify overload constraints on all existing algorithms one-by-one, then add in the overloads for ranges, at this point the code and requirements would be ugly. After concepts is added to the language, we'll both hopefully have a set of concise concepts that describe what the parameters should be, and a language feature that makes the implementation easy and clean.

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

Originally, iterators were sufficient, and they decouple containers from algorithms. Ranges could have been added then, but the language machinery for clean range interpretation of containers was somewhat lacking (decltype, for example, is useful), and it wasn't strictly required. Since then, range support has been desired, but it isn't easy to do it cleanly, and there is (on the horizon) a language extension that will make it much cleaner and easier.

Upvotes: 6

BeyelerStudios
BeyelerStudios

Reputation: 4283

You could 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 to support arrays:

template<typename T, size_t N>
T* min_element(T (&arr)[N]) { return std::min_element(arr, arr + N); }

Upvotes: 4

Maksim Solovjov
Maksim Solovjov

Reputation: 3157

This is because STL algorithms are container-independent. Iterators provide a uniform way for them to work, with the only limitation being what are the guarantees this algorithm requires from these iterators.

For example, if you want to do a linear search for min_element(), you only need forward iterators (i.e. they only have to support operator++). So, you can write one simple templated implementation that will work with essentially every container, despite how the container is implemented under the hood.

You could overload functions to take only the container and apply begin() and end() on them, but this would mean that you have one more interface to remember.

Edit

I suppose there are a few other arguments that could be made. Since STL was all about mathematical beauty and emphasis that algorithms are separate from containers, always passing iterators would reinforce this notion.

On the other hand, in terms of the C++ language as a whole, one of the main goals of Stroustrup was to educate developers. The full power of STL algorithms comes from the ability to pass arbitrary iterator ranges, but most of the time you want to operate on the whole container. If you provided overloads for the whole container, it could be argued that a large number of people would never bother to learn to use range versions, because it would be precisely those versions that would fall into "another interface to remember" category.

Upvotes: 7

Most of the time, the standard library is designed to provide the minimal interface necessary to accomplish all the tasks required, i.e. it tries to avoid interface bloat. You can operate on a whole container when the algorithm accepts a pair of iterators, but you could not operate on a subrange if the algorithm accepted a container. So the iterator pair is more fundamental, and so that's what the standard library provides. Convenience functions are usually not included.

However, you're certainly not the first person to think this way, and there's the entire Boost.Range library devoted to treating a range (both a container and an arbitrary range) as a single entity instead of a pair of iterators.

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

Upvotes: 43

Vlad from Moscow
Vlad from Moscow

Reputation: 310990

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

The program output is

1
1

I made such a suggestion for algorithms std::sort and std::reverse. You can read about it in my personal forum that I support like my perosnal internet page. here

Though it is written in Russian you can translate it for example with Bing or google.

Upvotes: -1

Related Questions