我想知道为什么STL不会重载它们的算法函数,这样我就可以通过简单地提供一个容器来调用它们,而不是采用更冗长的方式来传递begin + end迭代器。我当然理解为什么我们也想使用迭代器对来处理容器/数组的子序列,但是,几乎所有对这些方法的调用都使用整个容器:
std::for_each(myVector.begin(), myVector.end(), doSomething);
我发现只需编写
就更方便,可读和可维护std::for_each(myVector, doSomething);
有没有理由STL不提供这些重载? [编辑:我不是要用这个受限制的替换接口,而是也提供基于容器的接口!]它们是否会引入歧义?我正在考虑这样的事情:
template<typename _Container, typename _Funct>
inline _Funct for_each(_Container c, _Funct f) {
return for_each(begin(c), end(c), f);
}
我错过了什么吗?
答案 0 :(得分:18)
他们做为许多算法引入歧义。很多<algorithm>
看起来像
template<class iterator>
void do_something(iterator, iterator);
template<class iterator, class funct>
void do_something(iterator, iterator, funct);
如果添加其他重载
template<class container, class funct>
void do_something(container, funct);
编译器在确定do_something(x, y)
的含义时会遇到一些麻烦。 如果 *)功能 x
和y
具有相同的type
,则它会匹配iterator = type
和container = type, funct = type
。
C ++ 11试图用"concepts"解决这个问题,它可以识别容器和迭代器之间的区别。然而,这些“概念”变得过于复杂而无法达到标准,因此这些超载也没有。
*) 编译器在这里不同意,Comeau编译器声称它不明确,g ++ 4.5和MSVC 10调用第一个函数。
在评论中进行了长时间的讨论之后,这里有一个例子,它没有按预期工作 - 使用一个也可以兼作谓词的容器适配器。
#include <iostream>
#include <vector>
template<class iterator>
void test(iterator, iterator)
{
std::cout << "test iterator\n";
}
template<class iterator, class predicate>
void test(iterator, iterator, predicate)
{
std::cout << "test iterator, predicate\n";
}
template<class container, class predicate>
void test(const container& cont, predicate compare)
{
std::cout << "test container, predicate\n";
test(cont.begin(), cont.end(), compare);
}
template<class container>
class adapter
{
public:
typedef typename container::iterator iterator;
adapter(container* cont) : cont(cont)
{ }
iterator begin() const
{ return cont->begin(); }
iterator end() const
{ return cont->end(); }
bool operator()(const iterator& one, const iterator& two)
{ return *one < *two; }
private:
container* cont;
};
int main()
{
std::vector<int> v;
adapter<std::vector<int>> a(&v);
test(a, a);
}
输出:
测试迭代器
答案 1 :(得分:10)
不幸的是,这是一个更普遍的问题;也就是说,迭代器旨在击败那些糟糕的C API和Java风格的“将算法作为每个单独容器的方法”解决方案。它们是第一代通用解决方案,毫无疑问,经过反思,它们不如我们花了二十年时间考虑它后可获得的其他可能的通用解决方案一样好。
添加这些容器重载只会对问题空间的一小部分进行频带辅助;甚至可能会在未来使事情变得更糟。解决方案是范围,C ++希望尽快引入。
答案 2 :(得分:3)
要理解我认为必须要理解C ++算法的哲学。让我们先问这个问题:
为什么C ++算法是作为自由函数而不是成员函数实现的?
答案很简单:避免实施爆炸。假设您有M
个容器和N
算法,如果您将它们实现为容器的成员,那么将会有M*N
个实现。这种方法存在两个(相关的)问题:
C ++通过将它们实现为自由函数来解决这些问题,因此您只有N
个实现。在容器上运行的每个算法都需要一对迭代器,它们定义了范围。如果你想要带容器的重载而不是迭代器对,那么标准必须为每个算法提供这样的重载,并且会有2*N
个实现,这几乎打乱了C ++分离的原因。首先是来自容器的算法,而这些函数中有一半没有做任何另一半无法完成的事情。
所以我认为这不是一个问题。为了避免一个单个参数,为什么要实现N
更多函数(这也会对其使用施加一些限制,例如你无法传递指针它)?但是,如果程序员希望在他们的实用程序中使用这些函数,他们可以随时使用基于标准算法的许多其他函数来实现它们!
您评论道:
嗯,2 * N实现实际上只有N个实现。其他N个是内联重载,直接调用算法的“真实”版本,所以它们只是一个标题。提供容器重载并不会破坏将算法与容器分开的目的,因为(正如您在我的示例中所见),他们可以使用模板来处理所有类型的容器。
基于这种逻辑,人们可以很好地争论M*N
算法。那么也要使它们成为成员函数(并在内部调用自由函数)?我相信很多OOP人都会喜欢
auto result = container.accumulate(val);
结束
auto result = std::accumulate(container.begin(), container.end(), val);
答案 3 :(得分:3)
以下是Herb Sutter博客的相关答案:Why no container-based algorithms。它显示了反例,就像博佩尔森在上面的答案中所做的那样。
答案 4 :(得分:2)
有Range Operators library有意解决这个问题。 详细程度被削减了几次。
你的例子看起来像这样:
auto newVector = myVector * doSomething;
是的,doSomething
- 没有括号。
来自shell的熟悉习语(使用std算法):
auto t = vector<int>{3,2,1,4} | sort | unique;
答案 5 :(得分:0)
应该指出的是,定义自己的琐碎包装以添加容器化版本非常容易。
例如:
template<typename Container, typename Func>
Func for_each(Container& c, Func f) {
return std::for_each(c.begin(), c.end(), f);
}
现在您可以进行所需的简单通话。没有歧义,因为你的包装器不在std命名空间中。您可以定义带有const Container&amp;的重载。如果你想要调用C ++ - 11 const迭代器方法的版本(例如cbegin()),我认为你需要以不同的方式命名包装器。我使用for_each_const。
答案 6 :(得分:0)
显然,正如其他用户提到的那样,这是一个棘手的问题,所以不幸的是,这已经很长时间了,标准库中仍然没有解决方案。但是,已有范围库可用,例如Boost :: Range和Adobe源库中的一个,它们不仅提供了您在问题中描述的界面的简单性,而且还提供了一些更高级的功能。
您的示例与Boost完美配合(我们在下面using namespace boost::range::adaptors
):
boost::for_each(myVector, doSomething);
我们也可以快速轻松地切片myVector
:
boost::for_each(myVector | sliced(10, 20), doSomething)
我们甚至可以将myVector
与另一个进行压缩,按谓词进行过滤,并在单个简单语句中对结果对的每个其他元素进行采样(这要求您在doSomethingElse中解包由{{1生成的元组) }}):
boost::combined