为什么我必须在STL的算法函数中明确地指定范围,即使我想处理整个容器?

时间:2022-09-10 23:52:07

When using STL's functions like sort() or min_element() I always have to specify the range by begin and end explicitly:

当使用STL的函数sort()或min_element()时,我总是必须从开始到结束明确地指定范围:

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.

编辑:我知道我可以将它封装到一个函数中,但是因为对于所有函数都必须这样做,如果有更好的方法,我希望避免这样做。

6 个解决方案

#1


43  

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.

然而,你肯定不是第一个有这种想法的人,这是一个整体的提升。Range库致力于将范围(容器和任意范围)作为一个实体而不是一对迭代器。

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

还有一个正式的提议,将Eric Niebler的range库合并到c++标准库的未来版本中。

#2


7  

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.

这是因为STL算法与容器无关。迭代器为它们提供了一种统一的工作方式,唯一的限制是该算法需要这些迭代器提供的保证。

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.

例如,如果您想对min_element()进行线性搜索,您只需向前迭代器(也就是说,它们只需支持操作符++)。因此,您可以编写一个简单的模板实现,该实现将基本上与每个容器一起工作,尽管容器是如何在引擎盖下实现的。

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.

您可以重载函数,只使用容器,并在它们上应用begin()和end(),但这意味着您需要记住一个更多的接口。

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.

我想还有一些其他的争论。由于STL关注的都是数学之美,强调算法是独立于容器的,所以总是通过迭代器来加强这一概念。

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.

另一方面,就c++语言整体而言,Stroustrup的主要目标之一是教育开发人员。STL算法的强大之处在于能够传递任意的迭代器范围,但是大多数时候您希望对整个容器进行操作。如果您为整个容器提供了重载,那么可以认为,很多人永远都不会费心去学习使用范围版本,因为正是这些版本会属于“另一个要记住的接口”类别。

#3


6  

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).

如果我们在哪里添加容器/范围重载,那么我们要么必须给它们新的名称(ick),要么修改现有的算法,使之变得超负载智能。(iterator, iterator, value)重载和(range, value, function)重载具有相同数量的参数,调用哪个参数很容易让编译器感到困惑(可能会出现意外结果)。

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.

最初,迭代器是充分的,它们将容器与算法分离开来。当时可以添加范围,但是用于容器的清洁范围解释的语言机制有点缺乏(例如,decltype是有用的),而且它不是严格要求的。从那时起,范围支持就被要求了,但是要干净地实现它并不容易,而且(在地平线上)有一种语言扩展可以使它更简洁、更容易。

#4


4  

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

#5


0  

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

#6


-1  

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

我对算法std::sort和std::reverse提出了这样的建议。你可以在我的个人论坛上看到我支持的,就像我的perosnal互联网页面一样。在这里

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

虽然它是用俄语写的,你可以用Bing或谷歌来翻译。

#1


43  

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.

然而,你肯定不是第一个有这种想法的人,这是一个整体的提升。Range库致力于将范围(容器和任意范围)作为一个实体而不是一对迭代器。

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

还有一个正式的提议,将Eric Niebler的range库合并到c++标准库的未来版本中。

#2


7  

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.

这是因为STL算法与容器无关。迭代器为它们提供了一种统一的工作方式,唯一的限制是该算法需要这些迭代器提供的保证。

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.

例如,如果您想对min_element()进行线性搜索,您只需向前迭代器(也就是说,它们只需支持操作符++)。因此,您可以编写一个简单的模板实现,该实现将基本上与每个容器一起工作,尽管容器是如何在引擎盖下实现的。

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.

您可以重载函数,只使用容器,并在它们上应用begin()和end(),但这意味着您需要记住一个更多的接口。

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.

我想还有一些其他的争论。由于STL关注的都是数学之美,强调算法是独立于容器的,所以总是通过迭代器来加强这一概念。

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.

另一方面,就c++语言整体而言,Stroustrup的主要目标之一是教育开发人员。STL算法的强大之处在于能够传递任意的迭代器范围,但是大多数时候您希望对整个容器进行操作。如果您为整个容器提供了重载,那么可以认为,很多人永远都不会费心去学习使用范围版本,因为正是这些版本会属于“另一个要记住的接口”类别。

#3


6  

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).

如果我们在哪里添加容器/范围重载,那么我们要么必须给它们新的名称(ick),要么修改现有的算法,使之变得超负载智能。(iterator, iterator, value)重载和(range, value, function)重载具有相同数量的参数,调用哪个参数很容易让编译器感到困惑(可能会出现意外结果)。

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.

最初,迭代器是充分的,它们将容器与算法分离开来。当时可以添加范围,但是用于容器的清洁范围解释的语言机制有点缺乏(例如,decltype是有用的),而且它不是严格要求的。从那时起,范围支持就被要求了,但是要干净地实现它并不容易,而且(在地平线上)有一种语言扩展可以使它更简洁、更容易。

#4


4  

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

#5


0  

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

#6


-1  

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

我对算法std::sort和std::reverse提出了这样的建议。你可以在我的个人论坛上看到我支持的,就像我的perosnal互联网页面一样。在这里

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

虽然它是用俄语写的,你可以用Bing或谷歌来翻译。