18

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

私は何かが足りないのですか?

4

7 に答える 7

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)ます。が同じである場合、との両方に一致します。xytypeiterator = typecontainer = type, funct = type*)

C ++ 11は、コンテナーとイテレーターの違いを認識できる「概念」を使用してこれを解決しようとしました。ただし、これらの「概念」は複雑すぎて標準にできないことが判明したため、これらの過負荷もありませんでした。

*) コンパイラーはここで同意しません、Comeauコンパイラーはそれが曖昧であると主張し、g++4.5とMSVC10は最初の関数を呼び出します。


コメントで非常に長い議論をした後、これが期待どおりに機能しない1つの例です。述語としても使用できるコンテナーアダプターを使用します。

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

}

出力:

テストイテレータ

http://ideone.com/wps2tZ

于 2012-12-22T15:18:57.780 に答える
10

残念ながら、これははるかに一般的な問題です。つまり、そのイテレータは、これらのくだらないCAPIとJavaスタイルの「アルゴリズムを個々のコンテナのメソッドとして配置する」ソリューションを打ち負かすように設計されています。これらは第1世代のジェネリックソリューションであり、考えてみると、20年かけて考えた結果、他の可能なジェネリックソリューションほど良くなかったのは当然のことです。

これらのコンテナのオーバーロードを追加することは、問題のあるスペースのごく一部を支援するだけです。将来的には事態をさらに悪化させる可能性もあります。解決策は範囲であり、C++はこれをできるだけ早く導入しようとしています。

于 2012-12-22T14:37:08.613 に答える
3

それを理解するには、C++アルゴリズムの哲学を理解する必要があると思います。最初にこの質問をしましょう:

なぜC++アルゴリズムは、メンバー関数ではなくフリー関数として実装されているのですか?

答えは非常に簡単です。実装の急増を回避するためです。MコンテナとアルゴリズムがNあり、それらをコンテナのメンバーとして実装すると、M*N実装が行われます。このアプローチには2つの(関連する)問題があります。

  • まず、コードの再利用を利用していません。ほとんどの実装が繰り返されます。
  • 第二に、上記の直接的な結果である実装の爆発的増加。

C ++は、これらの問題を無料の関数として実装することで解決します。そのため、N実装のみが可能になります。コンテナで動作する各アルゴリズムは、範囲を定義するイテレータのペアを取ります。イテレータのペアではなく、コンテナを使用するオーバーロードが必要な場合、標準はアルゴリズムごとにそのようなオーバーロードを提供する必要があり、C++がアルゴリズムをコンテナから分離した理由そのものをほぼ無効にする実装があります。そもそも、これらの関数の半分は、残りの半分では実行できないことを何もしません。2*N

ですから、それほど問題ではないと思います。単一の引数を回避するために、なぜNより多くの関数を実装するのですか(ポインターを渡せないなど、使用法に制限があります)。ただし、プログラマーがユーティリティでそのような関数を必要とする場合は、標準アルゴリズムに基づいて、他の多くの関数と一緒にいつでもそれらを実装できます。


あなたはコメントしました:

ええと、2*Nの実装は実際にはNの実装だけです。他のN個は、アルゴリズムの「実際の」バージョンを直接呼び出すインラインオーバーロードであるため、ヘッダーのみのものです。コンテナーのオーバーロードを提供しても、アルゴリズムをコンテナーから分離する目的が損なわれることはありません。(私の例でわかるように)テンプレートを使用してすべてのタイプのコンテナーを処理できるためです。

この論理に基づいて、M*Nアルゴリズムについて非常によく議論することができます。では、それらもメンバー関数にします(そして、内部で無料の関数を呼び出します)?多くのOOPの人が好むと確信しています

auto result = container.accumulate(val);

以上

auto result = std::accumulate(container.begin(), container.end(), val);
于 2012-12-22T14:45:57.663 に答える
3

HerbSutterのブログからの関連する回答は次のとおりです。コンテナベースのアルゴリズムがない理由。これは、BoPerssonが上記の回答で行ったのと同じように反例を示しています。

于 2014-10-01T03:41:20.197 に答える
2

これを修正することを目的としたRangeOperatorsライブラリがあります。冗長性が数回カットされました。

あなたの例は次のようになります。

auto newVector = myVector * doSomething;

はい、doSomething-括弧なしです。

シェルからのなじみのあるイディオム(stdアルゴリズムを使用):

auto t = vector<int>{3,2,1,4} | sort | unique; 
于 2012-12-22T14:49:24.817 に答える
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&を使用するオーバーロードを定義できます。C ++ -11定数イテレータメソッド(cbegin()など)を呼び出すバージョンが必要な場合は、ラッパーに別の名前を付ける必要があると思います。for_each_constを使用します。

于 2015-03-15T01:46:32.223 に答える
0

明らかに、他のユーザーが述べたように、それはトリッキーな問題であるため、残念ながらそれは長い間であり、標準ライブラリにはまだ解決策がありません。ただし、Boost::RangeやAdobeSourceLibrariesのライブラリなど、すでに利用可能な範囲ライブラリがあり、質問で説明するインターフェイスの単純さだけでなく、いくつかのより優れた機能も提供します。

あなたの例はBoostで完全に機能します(以下にありusing namespace boost::range::adaptorsます):

boost::for_each(myVector, doSomething);

myVectorすばやく簡単にスライスすることもできます。

boost::for_each(myVector | sliced(10, 20), doSomething)

別のものでzipmyVectorし、述語でフィルタリングし、結果のペアの他のすべての要素を1つの単純なステートメントでサンプリングすることもできます(これには、doSomethingElseで生成されたタプルを解凍する必要がありますboost::combined):

boost::for_each( boost::combined(myVector, myOtherVector) | strided(2), doSomethingElse)

于 2017-05-22T19:31:20.967 に答える