operator <<
はいつ挿入演算子を参照し、いつビット単位の左シフトを参照しますか?
これは10
を出力し、operator <<
は左シフトを示します。
cout << a.b() << a.a.b << endl;
そして、これは11
を出力します、operator <<
は挿入演算子を参照します。
cout << a.b();
cout << a.a.b ;
混乱しています。operator <<
(cout
と一緒に使用する場合)はいつ左シフト演算子を参照しますか?
#include <iostream>
using namespace std;
class A {
public:
A() { a.a = a.b = 1; }
struct { int a, b; } a;
int b();
};
int A::b(){
int x=a.a;
a.a=a.b;
a.b=x;
return x;
};
int main(){
A a;
a.a.a = 0;
a.b();
cout << a.b() << a.a.b << endl; // ?????
return 0;
}
あなたが直面している問題は、<<演算子に関するものではありません。いずれの場合も、挿入演算子が呼び出されます。
ただし、コマンドラインでの評価の順序に関する問題に直面しています。
_cout << a.b() << a.a.b << endl;
_
関数a.b()
には副作用があります。値a.a.aとa.a.bを交換します。したがって、値ov _a.a.b
_を評価する前または後にa.b()が呼び出されるかどうかは明らかです。
C++では、評価の順序は指定されていません。詳細については、 cppreference.com を参照してください。
これは10を出力し、operator <<は左シフトを参照します。
cout << a.b()<< a.a.b << endl;
これは、オペランドの評価順序が指定されていないことが原因です。 clangでは11を出力しますが、gccでは10を出力します。
あなたのコード:
_cout << a.b() << a.a.b << endl;
_
次のように置き換えることができます:
_std::cout.operator<<(a.b()).operator<<(a.a.b);
_
clangは最初にa.b()
を評価し、次に_a.a.b
_を評価し、g ++はその逆を行います。 a.b()
は変数を変更するため、異なる結果が得られます。
コードを次のように書き直す場合:
_cout << a.b();
cout << a.a.b ;
_
次に、2つの完全な式ステートメントがあります。ここでは、オペランドの評価に関連する不特定の動作はありません。したがって、常に同じ結果が得られます。
あなたの場合、すべての_operator <<
_は、左の引数が_ostream&
_型であり、左から右にグループ化されるため、出力ストリーム挿入演算子です。
出力の違いは、関数の引数の評価の順序によって引き起こされます。
_cout << a.b() << a.a.b
_
です
_operator<<(operator<<(cout, a.b()), a.a.b)
_
したがって、出力は、_a.a.b
_またはa.b()
のどちらが最初に評価されるかによって異なります。これは実際には現在の標準(C++ 14)で指定されていないため、_11
_も取得できます。
C++ 17のAFAIK _11
_は、関数パラメーターの左から右への評価を強制するため、両方の場合の唯一の有効な出力になります。
更新:委員会が( N4606 の時点で) P0145R2 の下部に記載されている不確定に順序付けられたパラメーター評価を行うことを決定したため、これは正しくないようです。 [expr.call]/5を参照してください。
Update2:ここではオーバーロードされた演算子について話しているので、N4606の[over.match.oper]/2が適用されます。
ただし、オペランドは、組み込み演算子に指定された順序で順序付けられます。
したがって、実際、評価の順序はC++ 17で明確に定義されます。この誤解は明らかにP0145の作者によって予測されています:
このような非決定性が実質的な追加の最適化の利点をもたらすとは考えていませんが、関数呼び出しの評価の順序に関する混乱と危険を永続させます
この呼び出し:
cout << a.b() << a.a.b << endl;
最初に検討します:
cout << a.b()
これは挿入演算子に対応し、coutに参照を返します。したがって、命令は次のようになります。
(returned reference to cout) << a.a.b
これも挿入演算子などを呼び出します。
あなたの指示があった場合:
cout << (a.b() << a.a.b) << endl;
括弧の間の部分が最初に考慮されます:
a.b() << a.a.b
今回は、2 int
の間に演算子があります。コンパイラは、ビット演算子の呼び出しとしてのみ解決できます。
_<<
_などの二項演算子には、その使用法を定義する2つのプロパティがあります。(演算子)優先順位と(左または右)結合性です。この場合、結合性が鍵となります。たとえば、 http://en.cppreference.com/w/c/language/operator_precedence 、_<<
_演算子には左から右への結合性があるため、(角かっこで囲まれたように)順序付けられます。左から右へ:
_((cout << a.b()) << a.a.b) << endl;
_
または、cout << a.b()
、_<< a.a.b
_、_<< endl
_の順に並べられた単語です。
この順序付けの後、演算子のオーバーロードは、指定されたタイプで_<<
_を呼び出すたびに有効になります。これにより、呼び出されるオーバーロードが決定され、それがcout
-操作かシフトかが決まります。
括弧なしで、_<<
_の両側のオペランドが意味を決定します:_int << int == shift
_、_stream << any == insertion
_。オペレーターのこの「再利用」は、混乱を招き、死んでしまう可能性があります。ただし、括弧を使用してあいまいさを解決できます:stream << (int << int) == "int"