すべての<algorithm>関数がコンテナーではなく範囲のみを取得するのはなぜですか?


49

には多くの便利な関数がありますが<algorithm>、それらはすべて「シーケンス」、つまりイテレータのペアで動作します。たとえば、コンテナがあり、そのstd::accumulate上で実行したい場合、次のように書く必要があります。

std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);

私がやろうとしているのは:

int sum = std::accumulate(myContainer, 0);

私の目では、これはもう少し読みやすく、はっきりしています。

コンテナの一部のみを操作したい場合があるかもしれないので、範囲を渡すオプションがあることは間違いなく便利です。しかし、少なくとも私の経験では、それはまれな特別なケースです。私は通常、コンテナ全体を操作したいと思うでしょう。

これは、コンテナとのコールを取るラッパー関数書くのは簡単だbegin()し、end()その上に、このような便利な機能が標準ライブラリに含まれていません。

このSTL設計の選択の背後にある理由を知りたい。


7
STLは通常、便利なラッパーを提供しますか、それとも古いC ++の「ツールの今すぐ行く」「自分で足を踏み入れる」ポリシーに従っていますか?
キリアンフォス14年

2
レコードについては、独自のラッパーを記述するのではなく、Boost.Rangeのアルゴリズムラッパーを使用する必要があります。この場合、boost::accumulate
ecatmur

回答:


40

...範囲を渡すオプションがあることは間違いなく便利です。しかし、少なくとも私の経験では、それはまれな特別なケースです。私は通常、コンテナ全体を操作したいと思うでしょう

あなたの経験ではまれな特別なケースかもしれませんが、実際にはコンテナ全体 が特別なケースであり、任意の範囲は一般的なケースです。

現在のインターフェイスを使用してコンテナケース全体を実装できることに気づきましたが、逆はできません。

そのため、ライブラリ作成者は、2つのインターフェースを事前に実装するか、すべてのケースをカバーする1つのインターフェースのみを実装するかを選択できました。


コンテナを取得してbegin()およびend()を呼び出すラッパー関数を書くのは簡単ですが、そのような便利な関数は標準ライブラリには含まれていません

特に、無料の機能が含まれてstd::beginおりstd::end、現在含まれています。

したがって、ライブラリが便利なオーバーロードを提供するとしましょう。

template <typename Container>
void sort(Container &c) {
  sort(begin(c), end(c));
}

また、比較ファンクタを使用して同等のオーバーロードを提供する必要があり、他のすべてのアルゴリズムに同等のオーバーロードを提供する必要があります。

しかし、少なくともフルコンテナで操作したい場合はすべてカバーしましたよね?まあ、そうではありません。検討する

std::for_each(c.rbegin(), c.rend(), foo);

コンテナの逆方向の操作を処理する場合は、既存のアルゴリズムごとに別のメソッド(またはメソッドのペア)が必要です。


したがって、範囲ベースのアプローチは、単純な意味でより一般的です:

  • コンテナ全体のバージョンでできることはすべて実行できます
  • コンテナ全体のアプローチは、必要なオーバーロードの数を2倍または3倍にしますが、それでもなお強力ではありません。
  • 範囲ベースのアルゴリズムも構成可能です(反復子アダプターをスタックまたはチェーンできますが、これは関数型言語とPythonでより一般的に行われます)

もちろん、別の正当な理由があります。それは、STLを標準化するためにすでに多くの作業が行われていたことです。興味のある方は、Stepanov&Leeの技術レポートをここで見つけることができます

コメントで述べたように、Boost.Rangeは標準への変更を必要とせずに新しいアプローチを提供します。


9
OPを含む誰もが、特別なケースごとにオーバーロードを追加することを提案しているとは思いません。「コンテナ全体」が「任意の範囲」よりも一般的ではなかったとしても、「コンテナ全体、反転」よりもはるかに一般的です。に制限しf(c.begin(), c.end(), ...)、おそらく最も一般的に使用されるオーバーロード(ただし、それを決定する)だけにして、オーバーロードの数が2倍になるのを防ぎます。また、イテレーターアダプターは完全に直交しています(ご存じのとおり、Pythonで正常に動作しますが、イテレーターの動作は非常に異なっており、ほとんどの機能を備えていません)。

3
私は同意転送、容器全体をケースは非常に一般的ですが、それは疑問が提案よりも、可能な用途の多くのより小さなサブセットだということを指摘したかったです。具体的には、選択はコンテナ全体と部分コンテナの間ではなく、コンテナ全体と部分コンテナの間である可能性があります。また、アルゴリズムのオーバーロードも変更する必要がある場合、アダプターを使用することで認識れる複雑さがより大きくなることを示唆するのは妥当だと思います。
役に立たない14年

23
STLが範囲オブジェクトを提供する場合、コンテナバージョンすべてのケースカバーすることに注意してください。例えばstd::sort(std::range(start, stop))

3
それどころか:構成可能な関数型アルゴリズム(マップやフィルターなど)は、コレクションを表す単一のオブジェクトを受け取り、単一のオブジェクトを返します。これらは、イテレーターのペアに似たものを使用しません。
svick 14年

3
マクロでこれを行うことができます:#define MAKE_RANGE(container) (container).begin(), (container).end()</ jk>
ラチェットフリーク14年

21

このまさに主題についてのハーブサッターによる記事があることがわかります。基本的に、問題はオーバーロードのあいまいさです。次の場合:

template<typename Iter>
void sort( Iter, Iter ); // 1

template<typename Iter, typename Pred>
void sort( Iter, Iter, Pred ); // 2

以下を追加します。

template<typename Container>
void sort( Container& ); // 3

template<typename Container, typename Pred>
void sort( Container&, Pred ); // 4

適切に区別するのが難しく4なり1ます。

提案されているが、最終的にC ++ 0xに含まれていない概念はそれを解決し、を使用してそれを回避することもできenable_ifます。一部のアルゴリズムでは、まったく問題ありません。しかし、彼らはそれに反対しました。

ここですべてのコメントと回答を読んだ後、rangeオブジェクトが最善の解決策だと思います。私は見てみると思いますBoost.Range


1
まあ、ただaを使用typename Iterすることは、厳密な言語にはあまりにもカモタイプです。私は、例えば好むだろうtemplate<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1template<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4(多分曖昧さの問題を解決するだろう)など
ヴラド

@Vlad:残念ながら、これはT[]::iterator利用可能なものがないので、普通の古い配列では機能しません。また、適切なイテレータはコレクションのネストされた型である必要はありませんstd::iterator_traits。定義するだけで十分です。
ファイアグラフィク

@firegurafiku:配列は、基本的なTMPトリックを使用して特殊なケースに簡単に追加できます。
Vlad

11

基本的にレガシーの決定。イテレーターの概念はポインターでモデル化されていますが、コンテナーは配列ではモデル化されていません。さらに、配列は渡すのが難しいので(長さには一般に型のないテンプレートパラメーターが必要です)、多くの場合、関数はポインターのみを使用できます。

しかし、ええ、後知恵では、決定は間違っています。begin/endまたはのいずれかから構築可能な範囲オブジェクトを使用したほうがよかったでしょうbegin/length。現在、_n代わりに複数の接尾辞アルゴリズムがあります。


5

それらを追加すると、パワーが得られず(呼び出し.begin().end()自分でコンテナ全体を実行できます)、ライブラリにもう1つ追加する必要があります。などなど

つまり、コンテナ全体のユーザーが1つの追加の関数呼び出しパラメーターを入力するのを防ぐために、追加のテンプレートセットを維持するのは面倒なことではないので、おそらくそこにはないでしょう。


9
それは私に力を与えないでしょう、それは本当です-しかし、結局、どちらもしませんしstd::getline、それでも、それは図書館にあります。拡張制御構造では、とのみifを使用してすべてを行うことができるため、私に力を与えないと言うことができますgoto。うん、不公平な比較、私は知っている;)...そう、私は私が何とか仕様/実装/メンテナンスの負担を理解できると思うが、それは我々がおよそここで話しているだけの小さなラッパーだ
致死-ギター

小さなラッパーはコードを作成するのに費用がかかりませんし、ライブラリ内にあることは意味がありません。
ebasconp 14年

-1

今では、http://en.wikipedia.org/wiki/C++11#Range-based_for_loopがの優れた代替手段std::for_eachです。明示的な反復子がないことを確認してください。

int a[5] = {1, 2, 3, 4, 5};
for (auto &i: a) { i *= 2; }

https://stackoverflow.com/a/694534/2097284から着想を得ています。)


1
<algorithm>必要な実際のアルゴリズムbeginendイテレータのすべてではなく、の単一の部分のみを解決しますが、利点は誇張することはできません!2009ishで最初にC ++ 03を試したとき、ループの定型的な理由でイテレーターから遠ざかっていましたが、幸運にもそうではありませんでした。C ++ 2014で11に再起動、言語C ++常にしてきたはずです、そして今、私はなしでは生きられない、信じられないほどのアップグレードでしたauto &it: them:)
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.