30

次のプログラムを検討してください。

#include <cstddef>
#include <cstdio>

void f(char const*&&)      { std::puts("char const*&&");      } // (1)
void f(char const* const&) { std::puts("char const* const&"); } // (2)

template <std::size_t N>
void f(char const (&)[N])  { std::puts("char const(&)[N]");   } // (3)

int main()
{
    const char data[] = "a";
    f(data);
}

どちらfを呼ぶべきですか?なんで?

3 つのコンパイラの最新リリース バージョンでは、この質問に対する答えが一致していません。

  • (1)は、プログラムがg++ 4.5.2を使用してコンパイルされたときに呼び出されます。
  • (2)は、プログラムがVisual C++ 2010 SP1を使用してコンパイルされたときに呼び出されます。
  • (3)は、プログラムがClang 3.0 を使用してコンパイルされたときに呼び出されます (トランク 127530)

異なる C++0x ドラフトでオーバーロードの解決ルールが大幅に変更されましたか? それとも、これらのコンパイラのうちの 2 つが本当に完全に間違っているのでしょうか? 最新の C++0x ドラフトに従って選択する正しいオーバーロードはどれですか?

4

3 に答える 3

12

まず、最初の 2 つの場合、左辺値変換 (左辺値から右辺値への変換) があることを除いて、3 つすべての変換シーケンスは同じですが、変換シーケンスの順序付けには使用されません。3 つすべてが完全に一致します (関数テンプレートの特殊化にはパラメーター type がありますchar const(&)[2])。

でルールを反復すると、13.3.3.2p3この段落で停止します

S1 と S2 は参照バインディング (8.5.3) であり、どちらも参照修飾子なしで宣言された非静的メンバー関数の暗黙的なオブジェクト パラメーターを参照せず、S1 は右辺値参照を右辺値にバインドし、S2 は左辺値参照をバインドします。

左辺値への右辺値参照のバインドが必要な場合、変換シーケンスを形成することはできません。仕様は 13.3.3.1.4p3 で述べています。8.5.3p5 の最後の箇条書きで参照バインディングがどのように機能するかを見ると、配列の左辺値から型の一時 (rvalue 一時を意味していると思います)が作成され、その一時への参照がバインドされます。したがって、よりも優れているchar const*と思います。againstについても同じことが言えますが、 はテンプレートであるため必要ありませんが、同点の場合は再度選択します。(1)(2)(1)(3)(3)(1)

ではn3225、参照バインディング ルールを変更して、参照が右辺値にバインドされる限り、右辺値参照が左辺値であるイニシャライザ式にバインドできるようにしました (おそらく、以前に初期化子を適切に変換することによって作成されました)。これは、ここでは最新ではない可能性がある Visual C++ による処理に影響を与える可能性があります。

クランについてはよくわかりません。を無視(1)したとしても、 と の間で引き分けに(2)なり、非テンプレートであるため(3)選択する必要があります。(2)


8.5.3p5 の最後の箇条書きは「それ以外の場合は型の一時的な..」と書いてあるので紛らわしいと思います。13.3.3.1.4p3 で一時的な値が左辺値または右辺値と見なされるかどうかは明確ではありません。つまり、仕様の正確な言葉に従って、以下が実際にどのように動作するかわかりません。

void f(int &);
void f(int &&);

int main() {
  int n = 0;
  f(n);
}

13 節でテンポラリが右辺値として扱われると仮定すると、右辺値参照を 2 番目の関数で右辺値にバインドし、最初の関数で左辺値にバインドします。したがって、2 番目の関数を選択し、最後の箇条書き 8.5.3p5 までに診断を取得しT1ますT2。13 節でテンポラリが左辺値として扱われると仮定すると、以下は機能しません。

void f(int &&);
int main() {
  f(0);
}

右辺値参照を左辺値にバインドすると、節 13 によって関数が実行不可能になるためです。また、「右辺値参照を左辺値にバインドする」を、バインドされた最終的な式ではなく、初期化子式を参照するものとして解釈すると、次は受け入れられません。

void f(float &&);
int main() {
  int n = 0;
  f(n);
}

ただし、これは n3225 の時点で有効です。そのため、混乱が生じているようです。私はこれについて委員会に DR を送りました。

于 2011-03-18T12:53:19.130 に答える
4

#3 は、準拠するコンパイラによって選択された関数であると主張します。

(1) は (2) よりも優れているため、「S1 と S2 が参照バインディング (8.5.3) であり、どちらも非静的オブジェクトの暗黙的なオブジェクト パラメーターを参照していない場合、標準変換シーケンス S1 は標準変換シーケンス S2 よりも優れた変換シーケンスです。 ref-qualifier なしで宣言されたメンバー関数であり、S1 は右辺値参照を右辺値にバインドし、S2 は左辺値参照をバインドします。」

(3) は、(1) と (2) の両方よりも優れています。これは、恒等変換であるため (他は完全一致変換です)、「S1 が適切なサブシーケンスである場合、標準変換シーケンス S1 は標準変換シーケンス S2 よりも優れた変換シーケンスです。 S2 の (左辺値変換を除いて、13.3.3.1.1 で定義された標準形式の変換シーケンスを比較する; 恒等変換シーケンスは、任意の非恒等変換シーケンスのサブシーケンスと見なされる)"

テンプレートと非テンプレートは、どちらの変換も優れていない場合にのみ考慮されます。

奇妙なことに、Comeau は (3) よりも (2) を好みます。このテスト ケースはコンパイルに失敗します。

#include <cstddef>
#include <cstdio>

// (1) removed because Comeau doesn't support rvalue-references yet
char f(char const* const&) { std::puts("char const* const&"); return 0; } // (2)

template <std::size_t N>
int f(char const (&)[N])  { std::puts("char const(&)[N]"); return 0; } // (3)

int main()
{
    const char data[] = "a";
    switch (0) {
       case sizeof(char):
           break;
       case sizeof(f(data)):
           break;
    }
}
于 2011-03-18T03:16:17.613 に答える
2

これは、標準 (ドラフト 3225) からスニペットを収集するためのコミュニティ wiki の回答です。

セクション 13.3.3「実行可能な最善の機能」[over.match.best]

  1. ICSi(F) を次のように定義します。

    • が静的メンバー関数の場合F、ICS1(F) は、任意の関数に対して ICS1(F) が ICS1(G) より良くも悪くもならないように定義されG、対称的に、ICS1(G) は ICS1(F) よりも良くも悪くもありません。 ); それ以外は、

    • ICSi(F) は、リストの i 番目の引数を実行可能な関数 の i 番目のパラメーターの型に変換する暗黙の変換シーケンスを示しますF。13.3.3.1 は暗黙的な変換シーケンスを定義し、13.3.3.2 は、ある暗黙的な変換シーケンスが別の変換シーケンスよりも優れた変換シーケンスまたは悪い変換シーケンスであるとはどういう意味かを定義します。

    これらの定義が与えられた場合、実行可能な関数は、すべての引数iに対して、ICSi(F1) が ICSi(F2) よりも悪い変換シーケンスではない場合F1、別の実行可能な関数よりも優れた関数であると定義されます。F2

    • いくつかの引数jに対して、ICSj(F1) は ICSj(F2) よりも優れた変換シーケンスです

    または、そうでない場合は、

    • コンテキストはユーザー定義の変換 (8.5、13.3.1.5、および 13.3.1.6 を参照) による初期化であり、戻り値の型からF1宛先の型 (つまり、初期化されるエンティティの型) への標準の変換シーケンスは、戻り値の型F2から宛先の型への標準の変換シーケンスよりも優れた変換シーケンス

    または、そうでない場合は、

    • F1非テンプレート関数でF2あり、関数テンプレートの特殊化です

    または、そうでない場合は、

    • F1およびF2は関数テンプレートの特殊化であり、14.5.6.2 で説明されている半順序規則に従って、関数テンプレートF1は のテンプレートよりも特殊化されています。F2
  2. 他のすべての実行可能な関数よりも優れた関数である実行可能な関数が 1 つだけある場合、それはオーバーロードの解決によって選択された関数です。そうでない場合、呼び出しの形式が正しくありません。

セクション13.3.3.1.4「参照バインディング」[over.ics.ref]

  1. 参照型のパラメーターが引数式に直接 (8.5.3) バインドされる場合、引数式がパラメーター型の派生クラスである型を持たない限り、暗黙的な変換シーケンスは恒等変換です。シーケンスは派生から塩基への変換です (13.3.3.1)。パラメーターが変換関数を引数式に適用した結果に直接バインドされる場合、暗黙的な変換シーケンスはユーザー定義の変換シーケンス (13.3.3.1.2) であり、2 番目の標準変換シーケンスは恒等変換または、変換関数は、パラメーター型の派生クラスである型のエンティティ、派生からベースへの変換を返します。

  2. 参照型のパラメーターが引数式に直接バインドされていない場合、変換シーケンスは、13.3.3.1 に従って、引数式を参照の基になる型に変換するために必要なものです。概念的には、この変換シーケンスは、基になる型のテンポラリを引数式でコピー初期化することに対応します。トップレベルの cv-qualification の違いは、初期化自体に含まれ、変換にはなりません。

セクション13.3.3.2「暗黙の変換シーケンスのランキング」[over.ics.rank]

  1. 13.3.3.2 は、より良い変換シーケンスとより良い変換の関係に基づいて、暗黙的な変換シーケンスの部分的な順序付けを定義します。暗黙の変換シーケンスS1がこれらの規則によって よりも優れた変換シーケンスであると定義されてS2いる場合、それはS2よりも悪い変換シーケンスである場合でもありS1ます。変換シーケンスS1が変換シーケンスより良くも悪くもない場合S2、区別できない変換シーケンスS1S2言われます。

  2. 暗黙の変換シーケンスの基本形式を比較する場合 (13.3.3.1 で定義)

    • 標準の変換シーケンス (13.3.3.1.1) は、ユーザー定義の変換シーケンスまたは省略記号の変換シーケンスよりも優れた変換シーケンスです。

    • ユーザー定義の変換シーケンス (13.3.3.1.2) は、省略記号変換シーケンス (13.3.3.1.3) よりも優れた変換シーケンスです。

  3. 次の規則のいずれかが適用されない限り、同じ形式の 2 つの暗黙的な変換シーケンスは、区別できない変換シーケンスです。

    • 標準変換シーケンスは、次の場合S1に標準変換シーケンスよりも優れた変換シーケンスです。S2

      • S1 は S2 の適切なサブシーケンスです (13.3.3.1.1 で定義された標準形式の変換シーケンスを比較すると、左辺値変換は除外されます。恒等変換シーケンスは、任意の非恒等変換シーケンスのサブシーケンスと見なされます)。

      または、そうでない場合は、

      • のランクは のランクS1よりも優れているS2、または同じランクS1S2持ち、以下の段落のルールで区別できます

      または、そうでない場合は、

      • S1S2は修飾変換のみが異なり、それぞれ同様の型T1T2(4.4)を生成します。 T1type の cv-qualification シグネチャは、 type の cv-qualification シグネチャの適切なサブセットですT2

      または、そうでない場合は、

      • S1およびS2は参照バインディング (8.5.3) であり、どちらも参照修飾子なしで宣言された非静的メンバー関数の暗黙的なオブジェクト パラメーターを参照せず、S1右辺値参照を右辺値にS2バインドし、左辺値参照をバインドします。
于 2011-03-18T02:52:29.133 に答える