std :: setに「contains」メンバー関数がないのはなぜですか?


103

私は頻繁に使用してstd::set<int>おり、そのようなセットに数値が含まれているかどうかを確認する必要があるだけです。

私は書くのが自然だと思います:

if (myset.contains(number))
   ...

しかし、containsメンバーがいないため、面倒なことを書く必要があります。

if (myset.find(number) != myset.end())
  ..

またはそれほど明白ではない:

if (myset.count(element) > 0) 
  ..

この設計決定の理由はありますか?


7
ほとんどの標準ライブラリはイテレータで動作するため、通常、イテレータを返す関数が期待どおりのものです。それを抽象化する関数を書くのは難しいことではありません。ほとんどの場合、コンパイラはコードを1行または2行にするだけでインライン化し、同じパフォーマンスが得られます。
NathanOliver 2017年

3
このcount()アプローチのもう1つの(より基本的な)問題は、必要以上に多くの作業countains()を実行することです。
Leo Heinsaar 2017年

11
根本的な理由その設計上の決定の背後には、つまりcontains()返すboolでしょう要素がコレクション内にある場合についての貴重な情報を失いますfind()は、その情報をイテレータの形式で保存して返すため、STLのような一般的なライブラリに適しています。(ただし、a bool contains()があまり便利ではなく、必要でもないというわけではありません。)
Leo Heinsaar

3
contains(set, element)セットのパブリックインターフェイスを使用して無料の関数を記述するのは簡単です。したがって、セットのインターフェースは機能的に完全です。便利なメソッドを追加すると、C ++の方法ではない追加の機能を有効にすることなく、インターフェイスが増えるだけです。
Toby Speight 2017年

3
最近、すべてをクローズしていますか?この質問はどのように「主に意見に基づく」ですか?
Mr. Alien

回答:


148

それはおそらく彼らが作ろうとしていることstd::setstd::multiset、可能な限り類似しているためだと思います。(そして、にcountは完全に賢明な意味がありstd::multisetます。)

個人的にはこれは間違いだったと思います。

次のようにcountつづりを間違えcontainsて、テストを記述しても、それほど悪くはありません。

if (myset.count(element)) 
   ...

それでも残念です。


5
ちなみに、それはマップとマルチマップでまったく同じです(これは醜いですが、これらのとのすべての比較よりも醜くない.end()です)。
Matteo Italia

8
または、and の結果はの結果とまったく同じであるcontains()ため、冗長であるという理由で、追加のメンバーが必要ない場合もstd::set<T> sあります。条件式で値を使用すると、暗黙的ににキャストされるため、目的を十分に果たしていると感じた可能性があります。T ts.contains(t)static_cast<bool>(s.count(t))boolcount()
Justin Time-モニカ

2
スペルミス?if (myset.ICanHaz(element)) ...:D
ステフェイン・グーリッホン

3
@MartinBonner除外する理由が馬鹿げているかどうかは問題ではありません。また、会話が100%最終的な根拠ではなかったとしても、問題にはなりません。ここでのあなたの答えは、あなたがそれがどうあるべきかについての合理的な推測にすぎません。会話、およびそれに関与しているだけでなく、それを提案している誰かからの回答は(たとえ彼らがそうしなかったとしても)、どのように見ても、この推測よりも間違いなく真実に近いです。少なくとも、この回答では少なくともそれを言及する必要があります。これは大きな改善であり、責任を負うことになります。
Jason C

2
@JasonC:下にあるセクションを編集してください。私はあなたが言おうとしている点を本当に理解していません、そしてコメントはおそらくそれを明確にする最良の方法ではありません。ありがとう!
Martin Bonnerが

44

を書くことができるようにするにはif (s.contains())、のように(または別の話であるに変換可能な型)contains()を返すbool必要があります。boolbinary_search

根本的な理由設計上の決定の背後にはない、このようにそれを行うには、ということですcontains()これを返しますboolでしょう要素がコレクション内にある場合についての貴重な情報を失いますfind()は、その情報をイテレータの形式で保存して返すため、STLのような一般的なライブラリに適しています。アレックスステパノフがよく説明しているように(たとえば、ここ)、これは常にアレックスステパノフの指針となっています。

とおりcount()、それは大丈夫回避策はしばしばだが、一般的にアプローチ、それに問題があることです、それはより多くの作業し contains() なければならないでしょうが

これは、a bool contains()があまり便利ではなく、必要でもないということではありません。少し前に、この非常に同じ問題についてISO C ++標準-将来の提案グループで長い議論がありました。


5
そして、その議論はそれが望ましいことであるというほぼコンセンサスで終わり、あなたはそれについての提案を書くように求められたことに注目するのは興味深いことです。
PJTraill 2017年

@PJTraill真、そして私が先に進まなかった理由contains()は、明らかに、既存のコンテナーおよびアルゴリズムと強く相互作用し、C ++ 17に登場すると予想される時点で、概念と範囲によって強く影響を受けるであろうことです。私は(ディスカッションといくつかのプライベートな電子メール交換の結果として)最初にそれらを待つ方が良いアイデアであると確信しました。もちろん、2015年には、概念も範囲もC ++ 17に組み込まれないことは明らかではありませんでした(実際には、そうすることへの高い期待がありました)。しかし、今それを追求する価値があるかどうかはわかりません。
Leo Heinsaar 2017

1
std::set(質問がについて質問何です)、私がどのように表示されていないcountより多くの作業しcontainsなければならないでしょうが。のglibc実装countは(おおよそ)return find(value) == end() ? 0 : 1;です。三項演算子と戻り値の詳細!= end()(オプティマイザーで削除することを期待します)を除けば、これ以上の作業はありません。
Martin Bonnerがモニカ

4
"... contains()がブール値を返すと、要素がコレクション内のどこにあるかに関する重要な情報が失われます "-ユーザーがmyset.contains()(存在する場合)を呼び出すと、その情報が重要ではないことをかなり強く示します(そのコンテキストでユーザーに)。
キーストンプソン

1
なぜcount()必要以上の仕事contains()をするのstd::setですか?それはユニークなので、まったく同じであるcount()可能性がreturn contains(x) ? 1 : 0;あります。
Timmmm 2017年

22

誰もそれを追加しなかったので、それはそれを欠いています。stdライブラリが組み込んだSTLのコンテナは、インターフェイスが最小限になるように設計されているため、誰も追加しませんでした。(std::string同じ方法でSTLから取得されたものではないことに注意してください)。

奇妙な構文を気にしないのであれば、それを偽造することができます:

template<class K>
struct contains_t {
  K&& k;
  template<class C>
  friend bool operator->*( C&& c, contains_t&& ) {
    auto range = std::forward<C>(c).equal_range(std::forward<K>(k));
    return range.first != range.second;
    // faster than:
    // return std::forward<C>(c).count( std::forward<K>(k) ) != 0;
    // for multi-meows with lots of duplicates
  }
};
template<class K>
containts_t<K> contains( K&& k ) {
  return {std::forward<K>(k)};
}

使用する:

if (some_set->*contains(some_element)) {
}

基本的に、stdこの手法を使用して、ほとんどのC ++ 型の拡張メソッドを記述できます。

これを行うだけの方がはるかに理にかなっています。

if (some_set.count(some_element)) {
}

しかし、私は拡張メソッド方式で面白がっています。

本当に悲しいことは、1つまたは複数の要素を見つけなければならず、それらを数えなけれcontainsばならないので、効率的なものを書くことは、multimapまたはmultisetで速くなる可能性があるということです。count

7の10億コピーを含むマルチセット(使いきった場合に備えて)は、非常に遅くなる可能性があります。 .count(7)可能性がありますが、非常に速くなる可能性がありますcontains(7)

上記の拡張メソッドを使用lower_boundするとend、を使用してと比較し、次に要素と比較することで、このケースでの処理を高速化できます。ただし、順序付けられていないニャーと順序付けられたニャーに対してそれを行うには、空想的なSFINAEまたはコンテナ固有のオーバーロードが必要になります。


2
7の10億コピー?そして、ここでstd::setは重複を含めることはできないので、std::set::count常に0またはを返し1ます。
nwp 2017年

5
@nwp std::multiset::count
ミレニアム

2
@nwp backticks「セット」という単語の周りの私の欠如は、私がstd::set特に言及していないためです。気分を良くするために、マルチを追加します
Yakk-Adam Nevraumont 2017年

3
「ニャー」が参照となるはずだったものは何でもジョークを見逃しているようです。
user2357112は18:39にMonica

2
@ user2357112 meowは、「セットまたはマップ」のプレースホルダーです。STLに理由を尋ねてください。
Yakk-Adam Nevraumont 2017年

12

あなたは特定のケースを調査していて、全体像を見ていません。ドキュメントに 記載されているようにstd::setAssociativeContainerコンセプトの要件を満たしています。その概念のために、それは持っているどんな意味がありませんcontains、それはかなり役に立たないためであるとして、この方法をstd::multisetしてstd::multimap、しかし、countそれらのすべての罰金に動作します。この方法は、けれどもcontainsの別名として追加することができcountためstd::setstd::mapそして彼らのハッシュされたバージョン(のようなlengthsize()std::string)が、図書館クリエイターのように見えますが、それのための本当の必要性を見ていません。


8
これstringは怪物であることに注意してください。それはSTLより前に存在し、そこにlengthインデックスベースのすべてのメソッドがあり、STLモデル内に収まるように「コンテナ化」されました...下位互換性の理由で既存のメソッドを削除することなく。GotW#84を参照してください:Monoliths Unstrung => stringは、「メンバー関数の最小量」の設計原則に本当に違反しています。
Matthieu M.17年

5
しかし、問題は、「なぜこのようなAssociativeContainerの概念を持つ価値があるのか​​」ということになります。-そして、私はそれがそうであった後知恵を確信していない。
Martin Bonnerがモニカ

24
マルチセット、マルチマップ、またはマップに何かが含まれているかどうかを尋ねるのは、私にとっては理にかなっています。実際、containsセット/マップでの作業は同じですが、マルチセット/マルチマップより高速にできますcount
Yakk-Adam Nevraumont 2017年

5
AssociativeContainerはするクラスを必要としないではない持っているcontains方法を。
user2357112は

6
@Slavaそれは言っsize()empty()いるようなもので、重複していますが、多くのコンテナは両方を持っています。
Barry

10

なぜ、私は知らないが、std::set何も持っているcontainsが、countこれだけで今までに戻っ0たり1、あなたはテンプレート記述することができcontains、このようなヘルパー関数を:

template<class Container, class T>
auto contains(const Container& v, const T& x)
-> decltype(v.find(x) != v.end())
{
    return v.find(x) != v.end();
}

次のように使用します。

    if (contains(myset, element)) ...

3
-1これは、containsメソッドが実際に存在するという事実と正反対であり、愚かな方法で名前が付けられているだけです。
Matteo Italia 2017年

4
「STLの努めは、最小限のインターフェースを提供する」ベニハシガラス属 std::string
bolov

6
@bolov:あなたのポイント?std.::stringSTLの一部ではありません!これは標準ライブラリの一部であり、さかのぼってテンプレート化されました...
MFH

3
@MatteoItalia countfind、コードがと共有されてmultisetいる場合、範囲の開始と終了を取得するために2つのsを効果的に実行する必要があるため、遅くなる可能性があります。
Mark Ransom 2017年

2
OPはそれが冗長であることをすでに知っていますが、明らかにコードに明示的に読み取ってもらいたいのですcontains。私はそれで何も悪いとは思わない。@MarkRansom小さなSFINAEは、このテンプレートがバインドすべきでないものにバインドするのを防ぐためのものです。
rustyx 2017年

7

その真の理由setは私にとって謎ですが、この同じ設計の1つの考えられる説明はmap、人々が誤って非効率なコードを作成するのを防ぐことです。

if (myMap.contains("Meaning of universe"))
{
    myMap["Meaning of universe"] = 42;
}

これは2つになります mapルックアップます。

代わりに、イテレータを取得する必要があります。これにより、イテレータを再利用する必要があるという精神的なヒントが得られます。

auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
    position->second = 42;
}

mapルックアップを1つだけ使用します。

我々はそれを実現した場合setmap同じ肉で作られている、我々はにも、この原則を適用することができますset。つまり、にset存在する場合にのみアイテムを操作したい場合set、この設計により、次のようにコードを記述できなくなります。

struct Dog
{
    std::string name;
    void bark();
}

operator <(Dog left, Dog right)
{
    return left.name < right.name;
}

std::set<Dog> dogs;
...
if (dogs.contain("Husky"))
{
    dogs.find("Husky")->bark();
}

もちろん、これは単なる憶測にすぎません。


1
はい。ただし、intのセットの場合は適用されません。
Jabberwocky 2017年

7
人々がif (myMap.count("Meaning of universe"))うまく書けることを除いて、それで...?
Barry

@MichaelWalzおっと、あなたは正しい。答えを変更して、設定例も含めました。ただし、intのセットの推論は私には謎です。
マーティンドロジック2017年

2
これは正しくありません。彼らは同じように簡単にあなたの非効率的なコードを書くことができますcontainsのようにcount
Martin Bonnerがモニカ


0

binary_searchはどうですか?

 set <int> set1;
 set1.insert(10);
 set1.insert(40);
 set1.insert(30);
 if(std::binary_search(set1.begin(),set1.end(),30))
     bool found=true;

それはのために働きませんstd::unordered_setが、std::setそれのために働きます。
Jabberwocky

これは正常です。binary_searchはバイナリツリーに対してのみ機能します。
Massimiliano Di Cavio

0

contains()はブール値を返す必要があります。C ++ 20コンパイラを使用して、コードに対して次の出力を取得します。

#include<iostream>
#include<map>
using namespace std;

int main()
{
    multimap<char,int>mulmap;
    mulmap.insert(make_pair('a', 1)); //multiple similar key
    mulmap.insert(make_pair('a', 2)); //multiple similar key
    mulmap.insert(make_pair('a', 3)); //multiple similar key
    mulmap.insert(make_pair('b', 3));
    mulmap.insert({'a',4});
    mulmap.insert(pair<char,int>('a', 4));
    
    cout<<mulmap.contains('c')<<endl;  //Output:0 as it doesn't exist
    cout<<mulmap.contains('b')<<endl;  //Output:1 as it exist
}

-1

もう1つの理由は、std :: setが数学セット理論の意味でのセットであるという誤った印象をプログラマに与えることです。彼らがそれを実装すると、他の多くの質問が続きます:std :: setに値のcontains()がある場合、なぜ別のセットにそれがないのですか?union()、intersection()およびその他の集合演算と述語はどこにありますか?

もちろん、答えは、セット操作の一部はすでに(std :: set_union()など)の関数として実装されており、その他は単純にcontains()として実装されていることです。関数および関数オブジェクトは、オブジェクトメンバーよりも数学の抽象化でうまく機能し、特定のコンテナータイプに限定されません。

完全な数学セット機能を実装する必要がある場合、基盤となるコンテナーの選択だけでなく、実装の詳細も選択できます。たとえば、theory_union()関数は不変オブジェクトで機能し、関数型プログラミングにより適しています。または、オペランドを変更してメモリを節約しますか?それは最初から関数オブジェクトとして実装されますか、それともC関数であり、必要に応じてstd :: function <>を使用して実装する方が良いでしょうか?

現在のところ、std :: setは単なるコンテナーであり、数学的な意味でのsetの実装に適していますが、理論上のセットであるということは、理論上のベクトルであるstd :: vectorとほとんど同じです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.