13

min次の関数の定義

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u) -> decltype(t < u ? t : u)
{
    return t < u ? t : u;
}

問題があります:書くことは完全に合法であるようです

min(10, 20) = 0;

これは、Clang 3.5 および g++ 4.9 でテストされています。

解決策は簡単std::forwardです。引数の「右辺値」を復元するために使用するだけです。つまり、本体を変更してdecltype

t < u ? std::forward<T>(t) : std::forward<U>(u)

ただし、最初の定義でエラーが発生しない理由を説明するには途方に暮れています。


転送とユニバーサル参照についての私の理解を考えると、整数リテラルが渡されたときと同じように、 と の両方が引数の型tu推測します。int&&ただし、 の本体内ではmin、引数に名前があるため、それらは左辺値です。ここで、条件演算子の非常に複雑なルールが登場しますが、適切な行は次のとおりだと思います。

  • E2 [および] E3 はどちらも同じ型の glvalue です。この場合、結果の型と値のカテゴリは同じになります。

したがって、戻り値の型も同様である必要がありますoperator?:int&&ただし、(私が知る限り) Clang と g++ の両方がmin(int&&, int&&)左辺値参照を返すint&ため、結果に割り当てることができます。

明らかに私の理解にはギャップがありますが、何が欠けているのか正確にはわかりません。ここで何が起こっているのかを正確に説明できる人はいますか?


編集:

Niall が正しく指摘しているように、ここでの問題は条件演算子 (int&&期待どおりの型の左辺値を返す) ではなく、decltype. decltype言うためのルール

式の値カテゴリが左辺値の場合、decltype は T& を指定します

そのため、関数の戻り値は になりますint&& &。これは、C++ 11 の参照折りたたみ規則により、プレーンになりますint&(私の予想に反してint&&)。

しかし、 を使用すると、 (back)std::forwardの 2 番目と 3 番目の引数をoperator?:右辺値、具体的には xvalue に変換します。xvalues はまだ glvalues であるため (後ろに追いついていますか?)、同じ条件演算子規則が適用され、同じ型と値カテゴリの結果が得られます。つまりint&&、xvalue です。

ここで、関数が戻ると、別のdecltypeルールがトリガーされます。

式の値カテゴリが xvalue の場合、decltype は T&& を指定します

今回は、参照の折りたたみによりint&& && = int&&、さらに重要なことに、関数は xvalue を返します。これにより、私たちが望むように、戻り値に代入することが違法になります。

4

1 に答える 1

7

問題はdecltype()ルールにある可能性があります。

これはほのめかされています。末尾の戻り型が削除された場合

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u)
{
    return t < u ? t : u;
}

コンパイラがそれを推測することが許可されている場合、戻り値の型はintです。

decltype の使用

decltype ( expression )
...
式の値カテゴリが左辺値の場合、decltype は T& を指定します

cppreferenceから取得。

tandを含む式uは左辺値であるため (これらは左辺値であり、右辺値参照と呼ばれます)、戻り値は左辺値参照です。

この状況では、リテラルが変更される可能性がある状況につながります。「ユニバーサル参照」(または「転送参照」)および関連する参照折りたたみルールを使用する場合は、転送を慎重に使用する必要があります。

すでに述べたように、状況を修正するには、正しい使用法をstd::forward適用する必要があり、戻り値の型は期待されるものになります。


詳細std::forwardおよび参照の折りたたみについては、SO のこちらを参照してください。

于 2014-10-14T08:24:51.477 に答える