14

(comp.std.c++ でこの質問のバリエーションを尋ねましたが、回答が得られませんでした。)

f(arg)このコードの への呼び出しが の const ref オーバーロードを呼び出すのはなぜfですか?

void f(const std::string &); //less efficient
void f(std::string &&); //more efficient

void g(const char * arg)
{
     f(arg);
}

私の直感は、f(string &&)オーバーロードを選択する必要があることを示しています。なぜならarg、一時的なものに変換する必要があり、一時的なものは左辺値の参照よりも右辺値の参照によく一致するからです。

これはGCC とMSVC では発生しません (編集: Sumant に感謝: GCC 4.3-4.5 では発生しません)。少なくともG++ とMSVC では、作成された中間の一時変数がある場合でも、左辺値は右辺値参照引数にバインドされません。実際、const ref オーバーロードが存在しない場合、コンパイラはエラーを診断します。f(arg + 0) ただし、 orを書くと、期待どおりに右辺値参照のオーバーロードf(std::string(arg)) が選択されます。

f(string &&)C++0x 標準を読んだところ、const lvalue ref 引数を渡すときと同様に、実行可能かどうかを検討するときに、const char * から文字列への暗黙的な変換を考慮する必要があるようです。セクション 13.3 (オーバーロードの解決) では、あまりにも多くの場所で右辺値参照と const 参照を区別していません。また、中間の一時がある場合、左辺値が右辺値参照 (13.3.3.1.4/3) にバインドされないようにする規則は適用されないようです。結局のところ、一時から移動することは完全に安全です。

これは:

  1. 実装された動作が意図された動作である標準を誤解/誤解していますが、私の例がそのように動作する正当な理由がありますか?
  2. コンパイラベンダーが何らかの形で犯した間違いですか? それとも、一般的な実装戦略に基づく間違いですか? または、他のベンダーによってコピーされた、たとえば GCC (この左辺値/右辺値参照バインディング規則が最初に実装された場所) の間違いですか?
  3. 標準の欠陥、または意図しない結果、または明確にする必要があるものはありますか?

編集: 関連する追加の質問があります: C++0x 右辺値参照 - 左辺値-右辺値バインディング

4

6 に答える 6

8

FCDによると、GCCは間違っています。FCD は at8.5.3参照バインディングについて述べています

  • 参照が左辺値参照で、初期化式が [左辺値 / クラス型] の場合 ...
  • それ以外の場合、参照は非揮発性 const 型への左辺値参照 (つまり、cv1 は const ) であるか、参照は右辺値参照であり、初期化式は右辺値であるか、関数型を持っている必要があります。

初期化子がlvalueであるため、呼び出しのケースはstd::string &&どれにも一致しません。そのトップレベルの箇条書きにはすでに右辺値が必要であるため、一時的な右辺値を作成する場所には到達しません。

現在、オーバーロードの解決では、参照バインディングを直接使用して、暗黙的な変換シーケンスが存在するかどうかを確認することはありません。代わりに、それは言う13.3.3.1.4/2

参照型のパラメーターが引数式に直接バインドされていない場合、変換シーケンスは、13.3.3.1 に従って、引数式を参照の基になる型に変換するために必要なものです。

したがって、その勝者が実際にはその引数にバインドできない場合でも、オーバーロードの解決によって勝者が計算されます。例えば:

struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };

void f(int&);
void f(B);
int main() { A a; f(a.bits); }

参照バインディング at は、8.5ビットフィールドを左辺値参照にバインドすることを禁止します。しかし、オーバーロードの解決は、変換シーケンスが に変換されるものintであることを示しているため、後で呼び出しが行われたときに呼び出しが不正な形式であっても成功します。したがって、私のビットフィールドの例は形式が正しくありません。バージョンを選択する場合Bは成功しますが、ユーザー定義の変換が必要でした。

ただし、その規則には 2 つの例外があります。これらは

13.3.1 を参照する暗黙的なオブジェクト パラメーターを除き、非 const への左辺値参照を右辺値にバインドするか、右辺値参照を左辺値にバインドする必要がある場合、標準の変換シーケンスを形成することはできません。

したがって、次の呼び出しは有効です。

struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };

void f(int&); /* binding an lvalue ref to non-const to rvalue! */
void f(B);
int main() { A a; f(1); }

したがって、あなたの例はconst T&バージョンを呼び出します

void f(const std::string &);
void f(std::string &&); // would bind to lvalue!

void g(const char * arg) { f(arg); }

ただし、 と言うとf(arg + 0)、右辺値を作成するため、2 番目の関数が実行可能になります。

于 2010-05-01T10:38:45.630 に答える
6

それはあなたが読んだ標準草案の欠陥でした。この欠陥は、安全上の理由から左辺値への右辺値参照のバインドを許可しないようにするための熱心な編集の副作用として入り込みました。

あなたの直感は正しいです。もちろん、初期化子が左辺値式であったとしても、右辺値参照が名前のない一時的なものを参照できるようにしても害はありません。結局のところ、これが右辺値参照の目的です。あなたが確認した問題は、昨年修正されました。次の標準では、右辺値参照が一時的な文字列オブジェクトを参照する例で、2 番目のオーバーロードが選択されることが義務付けられます。

ルールの修正により、ドラフト n3225.pdf (2010-11-27) が作成されました。

  • [...]
  • それ以外の場合、参照は非揮発性 const 型への左辺値参照 (つまり、cv1 は const とする) であるか、参照は右辺値参照であり、初期化式は右辺値であるか、関数 type を持っている必要があります。[...]
    • [...]
    • それ以外の場合は、一時的な [...] が作成されます [...]
        double&& rrd3 = i; // rrd3 refers to temporary with value 2.0

しかし、N3225 はiこの例の内容を言い逃したようです。最新のドラフト N3290 には、次の例が含まれています。

        double d2 = 1.0;
        double&& rrd2 = d2; // error: copying lvalue of related type
        int i3 = 2;
        double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0

この問題が修正される前に MSVC バージョンがリリースされたため、古いルールに従って右辺値参照を処理します。次の MSVC バージョンでは、新しい右辺値参照規則 (MSVC 開発者によって「右辺値参照 2.1」と呼ばれる) が実装される予定です。リンクを参照してください。

于 2011-08-29T13:08:45.360 に答える
2

g++ で Doug が言及した動作は見られませんでした。g++ 4.5 と 4.4.3 は両方ともf(string &&)期待どおりに呼び出しますが、VS2010 はf(const string &). どの g++ バージョンを使用していますか?

于 2010-05-01T04:57:07.460 に答える
1

あなたが私に尋ねるならば、標準の現在の草案の多くのことは明確にする必要があります。コンパイラはまだ開発中なので、彼らの助けを信頼するのは難しい.

あなたの直感が正しいことは明らかです...あらゆる種類の一時変数は、右辺値参照にバインドすることになっています。たとえば、§3.10 の新しい「分類法」セクションでは、一時変数を右辺値として明確に定義しています。

問題は、RR 引数の指定が一時の作成を呼び出すには不十分である可能性があります。§5.2.2/5: 「パラメータが const 参照型である場合、必要に応じて一時オブジェクトが導入されます。」それは疑わしいほど排他的に聞こえます。

§13.3.3.1/6 で再び亀裂をすり抜けているようです: (強調は私のものです)

パラメーターの型が参照でない場合、暗黙的な変換シーケンスは、引数式からのパラメーターのコピー初期化をモデル化します。暗黙的な変換シーケンスは、引数式をパラメーターの型の prvalue に変換するために必要なものです。

コピー初期化string &&rr = "hello";は GCC で正常に機能することに注意してください。

編集:実際には、私のバージョンの GCC には問題はありません。ユーザー定義の変換シーケンスの 2 番目の標準変換が右辺値参照の形成にどのように関連するかをまだ理解しようとしています。(RR の形成は変換ですか? それとも、5.2.2/5 のような散在する情報によって決定されますか?)

于 2010-05-01T04:32:36.523 に答える
0

これを見てください:

http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx

右辺値参照: オーバーロードの解決

あなたのケースは次のように見えます:「左辺値は左辺値参照へのバインディングを強く好みます」。

于 2010-05-01T04:53:22.627 に答える
0

標準の最新バージョンでそれが変更されたかどうかはわかりませんが、以前は「疑わしい場合は右辺値参照を使用しないでください」のようなことを言っていました。おそらく互換性の理由です。

移動セマンティクスが必要な場合はf(std::move(arg))、両方のコンパイラで動作する を使用します。

于 2010-05-01T05:06:11.753 に答える