为什么我必须始终在 STL 的算法函数中显式指定范围,即使我想处理整个容器?
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?
当使用STL的函数(如sort()
或min_element()
)时,我总是必须明确地通过begin和end指定范围:
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;
}
如果我打算只在容器的一部分上工作,这是有意义的,但更多时候我需要函数在整个容器上工作。是否有一个重载函数不允许这样做的原因:
std::vector<int> list = {7, 3, 9, 1, 5, 2};
auto found_element = std::min_element(list);
是否有一种方法来完成我忽略的容器的总范围的函数调用?
EDIT:我知道我可以将其封装在一个函数中,但是因为这必须为所有函数完成,所以如果有更好的方法,我想避免这种情况。
大多数时候,标准库被设计为提供完成所有所需任务所需的最小接口,也就是说,它试图避免接口膨胀。当算法接受一对迭代器时,可以对整个容器进行操作,但如果算法接受容器,则不能对子范围进行操作。所以迭代器对是更基本的,这就是标准库提供的。方便函数通常不包括在内。
然而,你肯定不是第一个这样想的人,这是整个Boost。Range库致力于将范围(包括容器和任意范围)视为单个实体,而不是一对迭代器。
还有一个正式的建议,将Eric Niebler的range库合并到未来版本的c++标准库中。
这是因为STL算法与容器无关。迭代器为它们提供了一种统一的工作方式,唯一的限制是该算法对这些迭代器的保证是什么。
例如,如果你想对min_element()
进行线性搜索,你只需要前向迭代器(即它们只需要支持operator++
)。因此,您可以编写一个简单的模板实现,它基本上可以与每个容器一起工作,而不管容器在底层是如何实现的。
你可以重载函数,只接受容器,并在它们上应用begin()
和end()
,但这意味着你要记住一个接口。
编辑
我想还有一些其他的论点可以提出。由于STL完全是关于数学之美,并强调算法与容器是分开的,所以总是传递迭代器会强化这一概念。
另一方面,就c++语言整体而言,Stroustrup的主要目标之一是教育开发人员。STL算法的全部功能来自于传递任意迭代器范围的能力,但大多数情况下,您希望对整个容器进行操作。如果您为整个容器提供了重载,可能会有人争论说,很多人永远不会费心去学习使用范围版本,因为正是这些版本会落入"另一个需要记住的接口"的类别。
容器或范围重载尚未完成的实际原因与概念提案有关。
现在,算法接受一堆模板参数,并对它们提出要求。如果您传递的类型不符合要求,则它们可能无法编译或无法正常工作。
重载几乎总是涉及不同数量的形参。
如果我们要添加容器/范围重载,那么我们要么必须给它们起一个新名字(ick),要么修改现有的算法使其重载智能。(iterator, iterator, value)重载和(range, value, function)重载具有相同数量的参数,调用哪一个很容易使编译器混淆(并且可能发生意外结果)。
虽然我们可以一个接一个地指定所有现有算法的重载约束,然后为范围添加重载,但此时代码和需求将是丑陋的。在概念被添加到语言之后,我们都希望有一组简明的概念来描述参数应该是什么,以及一个使实现简单明了的语言特性。
由于兼容性或其他原因,这些算法实际上可能不会对现有算法过载,但即使这样也会更容易解决。
最初,迭代器就足够了,它们将容器与算法解耦。当时可以添加范围,但是对容器进行清晰范围解释的语言机制有些缺乏(例如,decltype很有用),并且不是严格要求的。从那时起,就需要范围支持,但是要干净利落地做到这一点并不容易,而且(即将出现的)一种语言扩展将使它更干净、更容易。
您可以自己实现:
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);
完整性: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));
}
和支持数组:
template<typename T, size_t N>
T* min_element(T (&arr)[N]) { return std::min_element(arr, arr + N); }
这是我认为使用宏是好的时候之一。只要确保在宏内计算表达式没有副作用就可以了。
#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 ( )
在您的例子中,这产生:
std::vector<int> list = {7, 3, 9, 1, 5, 2};
auto const found_element = std::min_element( RANGE_ARGS_C(list) );
自己定义这样的函数很容易。例如
#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;
}
程序输出为
1
1
我对算法std::sort
和std::reverse
做了这样的建议。你可以在我的个人论坛上读到它,我支持像我的个人网页。
虽然它是用俄语写的,但你可以用Bing或google翻译。
- 类型不可知的抽象以使用相同的运行时接口处理正向和反向迭代器和范围?
- 对象析构函数在多线程处理时不断被调用,但该对象并未超出范围
- 如何处理警告 C4177:#pragma 'float_control' 只能在全局范围或命名空间范围内使用
- 当数字可能超出C++中特定数据类型的范围时如何处理异常?
- 将代码从 C++ 移植到 C.如何处理这个基于矢量和范围的循环
- OPENCV图像处理,向量下标超出范围
- 循环直到整数输入在所需范围内无法处理非数字字符输入
- 用于处理超出范围错误的异常类 - C++
- 基于范围的for循环如何处理临时容器
- 基于范围的 for 循环和 std::vector: 是按顺序处理的元素
- 将我的字符保持在范围内,并处理它
- 如何处理错误“vtkTrivialProducer :此数据对象不包含请求的范围”
- 处理 360 度范围的最佳方式
- MSVC 是否错误地处理了类范围的静态与整数常量初始值设定项的链接
- 如何创建一个通用指针,它可以处理int和double变量并避免范围问题
- 处理范围内变量的内部函数
- 为什么我必须始终在 STL 的算法函数中显式指定范围,即使我想处理整个容器?
- 处理ASCII范围以外的字符
- 异常处理和范围
- 如何处理的一个范围中的多个数组