変換演算子のこのオーバーロードが選択されるのはなぜですか?


27

次のコードを検討してください。

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

ここで、2番目の変換演算子は、過負荷の解決によって選択されます。どうして?

私の理解する限り、2つの演算子はそれぞれoperator int &&() constとに推定されoperator int &() constます。どちらも実行可能な関数のセットに含まれています。[over.match.best]を読んでも、後者が優れている理由を理解するのに役立ちませんでした。

なぜ後者は前者よりも機能が優れているのですか?


私は単純な猫であり、他の2番目の猫でなければならないと私は言います。他の人の紹介はC ++ 11の重大な変更でした。それはどこかで標準になるでしょう。良い質問ですが、賛成票を持っています。(注意の専門家:このコメントが大食いの場合は教えてください!)
バトシェバ

3
FWIW、template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;最初のものを呼び出すためにそれを取得します。
NathanOliver

5
@LightnessRaceswithMonica変換演算子を使用すると、複数の変換演算子を持つことはできません。
NathanOliver

1
@Bathshebaそれが理由だとは思わない。これは、moveコンストラクターが重大な変更になるため、オーバーロードの解決では決して選択できないと言っているようなものです。moveコンストラクターを作成する場合は、その定義によってコードが「壊れる」ようにオプトインしています。
ブライアン、

1
@LightnessRaceswithMonica頭の中でまっすぐに保つ方法は、戻り値の型を関数の名前として扱うことです。タイプが異なれば名前も異なり、成功も同じ:)
NathanOliver

回答:


13

を返す変換演算子は、を返す変換演算子T&よりも特殊化されているため、推奨されますT&&

C ++ 17 [temp.deduct.partial] /(3.2)を参照してください。

変換関数の呼び出しのコンテキストでは、変換関数テンプレートの戻りの型が使用されます。

および/ 9:

、指定された型の、推論は両方向に成功した場合、両方の(すなわち、タイプは、上記の変換後に同じである)P、およびA(上記に言及したタイプで置換される前に)参照型であった: -もし引数テンプレートのタイプ左辺値参照であり、パラメータテンプレートからのタイプはそうではなかった。パラメータタイプは、少なくとも引数タイプほど特殊化されているとは見なされない。...


12

推定された戻り値変換演算子は少し奇妙です。しかし、核となる考えは、どちらを使用するかを選択する関数パラメーターのように機能するということです。

そして、過負荷解決ルールでT&&T&のどちらを決定するかT&。これは許可することです:

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

仕事に。 T&&左辺値と照合できますが、左辺値とユニバーサル参照オーバーロードの両方が使用可能な場合は、左辺値が優先されます。

変換演算子の正しいセットはおそらく次のとおりです。

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

あるいは

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

寿命の延長に失​​敗するのを防ぐため。

3順序を決定するために使用されるタイプは、部分的な順序付けが行われるコンテキストによって異なります。

[をちょきちょきと切る]

(3.2)変換関数の呼び出しのコンテキストでは、変換関数テンプレートの戻りの型が使用されます。

次に、オーバーロードを選択するときに、「より専門的な」ルールに依存します。

(9.1)引数テンプレートからの型が左辺値参照であり、パラメーターテンプレートからの型がそうでなかった場合、パラメーター型は少なくとも引数型ほど特殊化されているとは見なされません。さもないと、

したがってoperator T&&、少なくともほど特殊化されoperator T&operator T&いるわけではありませんが、ルールの状態は少なくともほど特殊化されていないoperator T&&ため、operator T&よりも特殊化されていませんoperator T&&

より特化したテンプレートは、オーバーロードの解決に勝ち、他のすべては同等です。


私が答えている間に誰かが言語弁護士タグを追加しました。単純に削除できます。どちらが呼び出されるかを選択するパラメーターとして扱われることを示すテキストと、テンプレートオーバーロードを選択するときに&&vs &がどのように扱われるかを説明するテキストを読んだ漠然とした記憶がありますが、正確なテキストを特定するのには時間がかかります。
Yakk-Adam Nevraumont、

4

intfrom を初期化しようとしていますany。そのためのプロセス:

  1. 私たちがそれを行う方法をすべて理解してください。つまり、すべての候補者を決定します。これらはint、標準の変換シーケンス([over.match.conv])を介して変換できる非明示的な変換関数に由来します。セクションには次のフレーズが含まれています。

    「への参照X」を返す変換関数の呼び出しは、型のglvalueであるXため、このような変換関数は、X候補関数を選択するこのプロセスで生成されると見なされます。

  2. 最適な候補者を選択してください。

ステップ1の後、2つの候補があります。operator int&() constoperator int&&() const、どちらもint候補関数を選択する目的で生成すると見なされます。利回りが最も高い候補はどれintですか。

右辺値の参照よりも左辺値の参照を優先するタイブレーカーがあります([over.ics.rank] /3.2.3)。ただし、ここでは実際に参照をバインドしているわけではなく、その例は多少逆になっています。これは、パラメーターが左辺値と右辺値の参照である場合です。

それが当てはまらない場合は、[over.match.best] /2.5タイブレーカーを使用して、より専門的な関数テンプレートを優先します。

一般的に言えば、経験則では、より具体的な変換が最も適しています。左辺値参照変換関数は、転送参照変換関数よりも具体的であるため、推奨されます。何もありませんint、我々は初期化しているがそれは右辺値が必要です(私たちが代わりに初期化されていたint&&、その後、operator T&() const候補なかったであろう)。


3.2.3は実際にT&&勝つと言いませんか?ああ、しかし、私たちは束縛される右辺値を持っていません。それとも私たちですか?私たちの候補者を選ぶとき、いくつかの単語は私たちがどのようにして得たのかを定義していなければなりません、int&そしてint&&それはバインディングでしたか?
Yakk-Adam Nevraumont

@ Yakk-AdamNevraumontいいえ、右辺値参照よりも左辺値参照を優先します。とにかく、それはおそらく間違ったセクションだと思います。「より専門的な」タイブレーカーは正しいものです。
バリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.