透明コンパレータとは何ですか?


106

C ++ 14では、連想コンテナはC ++ 11から変更されたようです– [associative.reqmts] / 13はこう言っています:

メンバ関数テンプレートはfindcountlower_boundupper_bound、およびequal_rangeタイプがない限り、オーバーロードの解決に参加してはならないCompare::is_transparent存在します。

コンパレータを「透明」にする目的は何ですか?

C ++ 14には、次のようなライブラリテンプレートも用意されています。

template <class T = void> struct less {
    constexpr bool operator()(const T& x, const T& y) const;
    typedef T first_argument_type;
    typedef T second_argument_type;
    typedef bool result_type;
};

template <> struct less<void> {
    template <class T, class U> auto operator()(T&& t, U&& u) const
    -> decltype(std::forward<T>(t) < std::forward<U>(u));
    typedef *unspecified* is_transparent;
};

たとえば、透明なコンパレータstd::set<T, std::less<T>>はありませんが、透明なコンパレータstd::set<T, std::less<>> あります。

これはどのような問題を解決しますか?これは標準のコンテナーの動作を変更しますか?例えば、のテンプレートパラメータはstd::setまだされているKey, Compare = std::less<Key>, ...ので、デフォルトのセットは、その負けないfindcountなどのメンバーを?


たとえば、このcppreferenceの説明を参照してください。そして、「メンバー関数テンプレート」という言葉に気付いているので、私は今愚かになっています...
Kerrek SB


cppreferenceには、en.cppreference.com
w /

回答:


60

これはどのような問題を解決しますか、

Dietmarの回答remyabelの回答を参照してください。

これにより、標準のコンテナの動作が変わりますか?

いいえ、デフォルトではありません。

などの新しいメンバー関数テンプレートのオーバーロードによりfind、キータイプ自体を使用する代わりに、コンテナーのキーと同等のタイプを使用できます。この機能を追加するための理論的根拠と詳細に書かれた提案については、JoaquínMªLópezMuñozによるN3465を参照してください。

ブリストル会議で、LWGは異種ルックアップ機能が有用かつ望ましいことで合意しましたが、ホアキンの提案がすべての場合において安全であるとは確信できませんでした。N3465の提案は、一部のプログラムに深刻な問題を引き起こしたでしょう(既存のコードへの影響のセクションを参照)。ホアキンは、異なるトレードオフのいくつかの代替実装を使用して更新されたドラフト提案を作成しました。これは、LWGが長所と短所を理解するのに非常に役立ちましたが、機能を追加するコンセンサスがなかったため、すべての方法でいくつかのプログラムを壊すリスクがありました。無条件で機能を追加することは安全ではありませんが、デフォルトで無効にされて「オプトイン」のみされている場合は安全であると判断しました。

N3657提案(これは私自身とN3465に基づくSTLによる土壇場の改訂であり、ホアキンによる後の未公開ドラフトでした)の主な違いはis_transparent、新しい機能にオプトインするために使用できるプロトコルとしてタイプを追加することでした。

「トランスペアレントファンクタ」(つまり、is_transparentタイプを定義するもの)を使用しない場合、コンテナは以前と同じように動作し、それがデフォルトです。

std::less<>(C ++ 14の新機能)または別の「透明なファンクター」タイプを使用することを選択した場合、新しい機能を利用できます。

std::less<>エイリアステンプレートを使用すると簡単に使用できます。

template<typename T, typename Cmp = std::less<>, typename Alloc = std::allocator<T>>
  using set = std::set<T, Cmp, Alloc>;

この名前is_transparentは、「ダイヤモンド演算子」をC ++ 14に追加したSTLのN3421に由来しています。「トランスペアレントファンクタ」は、任意の引数タイプ(同じである必要はありません)を受け入れ、それらの引数を別の演算子に転送するだけです。このようなファンクターは、たまたま連想コンテナでの異種ルックアップに必要なものなので、そのタイプis_transparentはすべてのひし形演算子に追加され、連想コンテナで新しい機能を有効にする必要があることを示すタグタイプとして使用されました。技術的には、コンテナには「透過的なファンクタ」は必要ありません。異種のタイプでの呼び出しをサポートするものだけです(たとえば、https://stackoverflow.com/a/18940595/981959pointer_compタイプを使用すると、問題を解決するために使用できます)。、STLの定義によれば透過的ではありません)。pointer_comp::is_transparentstd::set<T, C>タイプのキーを使用してTか、intその後、C唯一のタイプの引数を持つ呼び出し可能にする必要があるTint(いずれかの順序で)、それが本当に透明である必要はありません。この名前を使用した理由の1つは、適切な名前を付けることができなかったis_polymorphicためです(そのようなファンクターは静的な多態性を使用するため、私は好むでしょうがstd::is_polymorphic、動的な多態性を参照する型の特性がすでにあります)。


3
ねえ、あなたがSTLが言った「もちろん、あなたは頭の中でテンプレート引数の控除をすることができる」と言った人でしたか?
Kerrek SB、

10
いいえ、私はそこにはいませんでしたが、私よりもはるかに多くの準拠コンパイラを頭に持っている人々がいます:)
Jonathan Wakely

「ダイアモンド演算子」は<>リンクされた提案で参照されていると思いますが、その提案は導入されていません<>-空のテンプレートパラメータリストの既存の構文です。「ダイヤモンドオペレーターファンクタ」は少し混乱しにくいでしょう。
Qwertie

33

C ++ 11 find()ではlower_bound()、メンバーテンプレートなどはありません。つまり、この変更によって失われるものはありません。メンバーテンプレートはn3657で導入され、連想コンテナで異種キーを使用できるようになりました。良い例と悪い例を除いて、これが役立つ具体的な例はありません!

このis_transparent使用は、不要な変換を回避することを目的としています。メンバーテンプレートに制約がない場合、既存のコードは、メンバーテンプレートなしで変換されたオブジェクトを直接通過する可能性があります。n3657の使用例ではstd::set<std::string>、文字列リテラルを使用してオブジェクトを検索しています。C++ 11定義でstd::stringは、文字列リテラルを対応するメンバー関数に渡すときにオブジェクトが作成されます。変更により、文字列リテラルを直接使用することができます。基礎となる比較関数オブジェクトが排他的に実装されている場合は、比較ごとにが作成されるstd::stringため、これは悪いことstd::stringです。一方、基になる比較関数オブジェクトがstd::string 文字列リテラル。一時オブジェクトの構築を回避する場合があります。

is_transparent比較関数オブジェクトのネストされた型は、テンプレート化されたメンバー関数を使用する必要があるかどうかを指定する方法を提供します。比較関数オブジェクトが異種の引数を処理できる場合、この型を定義して、異なる引数を効率的に処理できることを示します。たとえば、新しい演算子関数オブジェクトは、単に委任しoperator<()、透明であると主張します。それは、少なくとも、引数としてstd::string取る演算子よりもオーバーロードが少ない場合にchar const*機能します。これらの関数オブジェクトも新しいので、間違った処理を行った場合(つまり、あるタイプの変換が必要な場合)は、少なくとも、サイレントな変更ではなく、パフォーマンスが低下します。


ありがとう-他の質問に対する私のコメントを参照してください:デフォルトで透過的な動作が得られますか?
Kerrek SB

8
@KerrekSB:is_transparent23.2.4 [associative.reqmts]パラグラフ13に従って比較関数オブジェクトで定義されている場合、透過的な動作が有効になります。デフォルトの比較関数オブジェクトはstd::less<Key>、23.4.2 [associative.map.syn]および23.4に従っています。 3 [associative.set.syn]。20.10.5 [比較]第4段落によれば、の一般的なテンプレートはネストされた型を定義してstd::less<...>いませis_transparentが、std::less<void>特殊化は定義してます。つまり、いいえ、デフォルトでは透過演算子はありません。
DietmarKühl、

命名のアイデアはありますか?なんでis_transparent
Plasmacel '19年

「これが役立つ具体的な例」が必要ですか?ここにあります私のユースケースは
spraff

19

以下は、すべてn3657からのコピーパスタです

Q. コンパレータを「透明」にする目的は何ですか?

A.連想コンテナルックアップ関数(find、lower_bound、upper_bound、equal_range)は、key_typeの引数のみを取り、ルックアップを実行するには、key_typeのオブジェクトを(暗黙的または明示的に)構築する必要があります。これはコストがかかる可能性があります。たとえば、コンパレーター関数がオブジェクトの1つのフィールドのみを参照する場合は、大きなオブジェクトを作成してセットを検索します。key_typeに匹敵する他のタイプを使用して検索できるようにしたいというユーザーの強い要望があります。

Q. これはどのような問題を解決しますか

A. LWGには、次のようなコードに関する懸念がありました。

std::set<std::string> s = /* ... */;
s.find("key");

C ++ 11では、これは単一のstd :: string一時を構築し、それを要素と比較してキーを見つけます。

N3465によって提案された変更により、std :: set :: find()関数は、const char *をコンパレーター関数std :: lessに渡し、一時的なstd :: stringを構築する制約のないテンプレートになります。すべての比較。LWGはこのパフォーマンスの問題を深刻な問題であると考えました。テンプレートのfind()関数は、ポインターのコンテナーでNULLを見つけることも防ぎます。これにより、以前は有効であったコードがコンパイルされなくなりますが、これはサイレントパフォーマンスの回帰よりも深刻ではない問題と見なされていました

Q. これにより、標準のコンテナーの動作が変わりますか

A.この提案は、ルックアップメンバー関数をメンバー関数テンプレートでオーバーロードすることにより、関連付けられたコンテナーを変更します。言語の変更はありません。

Q. デフォルトのセットは、検索、カウントなどのメンバーを失います。

A.新しいC ++ 14ライブラリ機能を比較関数として使用しない限り、メンバー関数は存在しないため、既存のC ++ 11コードのほとんどすべては影響を受けません。

Yakkを引用すると

C ++ 14では、Compare :: is_transparentが存在する場合、std :: set :: findはテンプレート関数です。渡す型はKeyである必要はなく、コンパレータの下で同等です。

およびn3657、

23.2.4 [associative.reqmts]に段落13を追加:メンバー関数テンプレートfind、lower_bound、upper_bound、equal_rangeは、タイプCompare :: is_transparent が存在ない場合を除き、オーバーロードの解決に参加しません

n3421「トランスペアレントオペレーターファンクタ」の例を提供します

完全なコードはここにあります


1
std::set<std::string>実際に「char const *パススルー」から利益を得ますか、それとも作成する必要がありstd::set<std::string, std::less<>>ますか?
Kerrek SB

@Kerrek私が間違っていなければ、 "char const *を渡す"が彼らが避けようとしていた問題だと思います。言葉遣いを見て:With the change proposed by N3465 the std::set::find() function would be an unconstrained template which would pass the const char* through to the comparator function, std::less<std::string>, which would construct a std::string temporary for every comparison. The LWG considered this performance problem to be a serious issue.

あなたの引用と13段落の鉱山は反対を言います:「型が存在しない/存在しない限り」...?!
Kerrek SB

4
@KerrekSB、それは私のせいです、N3657は「存在する」と言うはずでしたが、私は「存在しない」と書きました...それは最後の最後に書かれた遅い論文でした。ドラフト標準は正しいです。
ジョナサンウェイクリー、

3
はい、私が当時私が実際に言ったことではなく、私が言うつもりであったことを引用する方が明確かもしれません:)
Jonathan Wakely

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