このコードスニペットでcoutが「2 + 3 = 15」を出力するのはなぜですか?


126

以下のプログラムの出力はなぜそれが何であるのですか?

#include <iostream>
using namespace std;

int main(){

    cout << "2+3 = " <<
    cout << 2 + 3 << endl;
}

作り出す

2+3 = 15

期待の代わりに

2+3 = 5

この質問はすでに複数のクローズ/再開サイクルを行っています。

終了に投票する前に、この問題に関するこのメタディスカッションを検討しください。


96
;最初の出力行の終わりではなく、セミコロンが必要です<<。自分が印刷していると思うものを印刷していない。あなたはやっていますcout << cout、それは印刷します1(それは使用するcout.operator bool()と思います)。次に5(から2+3)がすぐ後に続き、15のように見えます。
Igor Tandetnik 2017年

5
@StephanLechnerそれはおそらくgcc4を使っているでしょう。特にgcc5まで完全に準拠したストリームはありませんでした。特にそれまでは暗黙的な変換がありました。
Baum mit Augen

4
答えの始まりのように聞こえる@IgorTandetnik。この質問には、最初に読んでも明らかではない微妙な点がたくさんあるようです。
Mark Ransom 2017年

14
なぜ人々はこの質問を閉じるために投票し続けるのですか?「このコードの何が悪いのか教えてください」ではなく、「なぜこのコードがこの出力を生成するのですか?」ではありません。最初の答えは「誤植です」ですが、2番目は、コンパイラがコードを解釈する方法、コンパイラエラーではない理由、ポインタアドレスの代わりに「1」を取得する方法の説明が必要です。
jaggedSpire 2017年

6
@jaggedSpireそれが誤植ではない場合、意図的なことを指摘せずに誤植のように見える異常な構造を故意に使用するため、それは非常に悪い質問です。いずれにせよ、近い投票に値する。(誤植、または悪意のある/悪意があるため。これは助けを求めている人のためのサイトであり、他の人をだまそうとしている人ではありません。)
David Schwartz

回答:


229

意図的であろうと偶然で<<あろうと、最初の出力行の終わりにあります;。だからあなたは本質的に持っています

cout << "2+3 = ";  // this, of course, prints "2+3 = "
cout << cout;      // this prints "1"
cout << 2 + 3;     // this prints "5"
cout << endl;      // this finishes the line

質問はこれに要約されます:なぜcout << cout;印刷するの"1"ですか?

これは、おそらく驚くべきことに、微妙です。std::coutは、その基本クラスを介して、次のようにブールコンテキストで使用することを目的とstd::basic_iosた特定の型変換演算子を提供します。

while (cout) { PrintSomething(cout); }

これは、出力を失敗させるのが難しいため、かなり貧弱な例std::basic_iosですが、実際には入力ストリームと出力ストリームの両方の基本クラスであり、入力の場合は、はるかに理にかなっています。

int value;
while (cin >> value) { DoSomethingWith(value); }

(ストリームの終わり、またはストリーム文字が有効な整数を形成しないときにループから抜けます)。

現在、この変換演算子の正確な定義は、標準のC ++ 03バージョンとC ++ 11バージョンの間で変更されています。古いバージョンではoperator void*() const;(通常はとして実装されていましたreturn fail() ? NULL : this;)、新しいバージョンでは(通常はexplicit operator bool() const;単にとして実装されていますreturn !fail();)。どちらの宣言もブール値のコンテキストでは問題なく機能しますが、そのようなコンテキストの外で(誤って)使用すると、動作が異なります。

特に、C ++ 03ルールでcout << coutは、cout << cout.operator void*()アドレスとして解釈され、出力されます。C ++ 11ルールの下cout << coutでは、演算子は宣言されexplicitているため、暗黙的な変換に参加できないため、まったくコンパイルしないでください。実際、それが変更の主な動機であり、無意味なコードがコンパイルされないようにしています。いずれかの標準に準拠するコンパイラは、印刷するプログラムを生成しません"1"

どうやら、特定のC ++実装では、コンパイラーとライブラリーを混合して一致させ、不適合な結果を生成することができます(@StephanLechnerを引用:「1を生成するxcodeの設定と、アドレスを生成する別の設定を見つけました:言語方言c ++ 98を「標準ライブラリlibc ++(c ++ 11をサポートするLLVM標準ライブラリ)」と組み合わせると1になり、c ++ 98をlibstdc(gnu c ++標準ライブラリ)と組み合わせるとアドレスが作成されます; ")。explicit変換演算子(C ++ 11の新機能)を認識しないC ++ 03スタイルのコンパイラと、変換をとして定義するC ++ 11スタイルのライブラリを組み合わせることができますoperator bool()。このような組み合わせを使用すると、cout << coutをと解釈することが可能になりcout << cout.operator bool()、これは単純cout << trueに印刷され"1"ます。


1
@TCこの特定の領域でC ++ 03とC ++ 98の間に違いがないと確信しています。問題の明確化に役立つ場合は、C ++ 03のすべての言及を「pre-C ++ 11」で置き換えることができると思います。Linuxなどでのコンパイラーとライブラリーのバージョン管理の複雑さにまったく詳しくありません。私はWindows / MSVCの人です。
Igor Tandetnik 2017年

4
私はC ++ 03とC ++ 98の間でつまづきをしようとしていませんでした。ポイントは、libc ++はC ++ 11以降のみであることです。C ++ 98/03に準拠しようとはしません。
TC

45

Igorが言うように、これはC ++ 11ライブラリで得られstd::basic_iosます。のoperator bool代わりにを持っていますが、operator void*どういうわけか宣言されていない(または扱われていない)explicit。正しい宣言については、こちらをご覧ください。

たとえば、適合するC ++ 11コンパイラは同じ結果を返します

#include <iostream>
using namespace std;

int main() {
    cout << "2+3 = " << 
    static_cast<bool>(cout) << 2 + 3 << endl;
}

しかし、あなたの場合、static_cast<bool>暗黙の変換として(間違って)許可されています。


編集:これは通常の動作ではないため、プラットフォームやコンパイラのバージョンなどを知っておくと便利です。


編集2:参考までに、コードは通常次のように書かれます

    cout << "2+3 = "
         << 2 + 3 << endl;

またはとして

    cout << "2+3 = ";
    cout << 2 + 3 << endl;

バグを露呈した2つのスタイルが混在しています。


1
最初に提案されたソリューションコードにタイプミスがあります。オペレーターが多すぎます。
eerorika

3
今私もそれをやっています、それは伝染性でなければなりません。ありがとう!
役に立たない

1
ハ!:)私の回答の最初の編集で、セミコロンを追加することを提案しましたが、行末の演算子に気づきませんでした。私はOPと共に、これが持つ可能性のあるタイプミスの最も重要な順列を生成したと思います。
eerorika

21

予期しない出力の理由はタイプミスです。あなたはおそらく意味した

cout << "2+3 = "
     << 2 + 3 << endl;

予期される出力を持つ文字列を無視すると、次のようになります。

cout << cout;

C ++ 11以降、これは形式が正しくありません。std::coutは暗黙のうちにstd::basic_ostream<char>::operator<<(またはメンバー以外のオーバーロード)が受け入れるものに変換できません。したがって、標準準拠のコンパイラは、少なくともこれを行うことについて警告する必要があります。私のコンパイラはプログラムのコンパイルを拒否しました。

std::coutはに変換可能boolであり、ストリーム入力演算子のboolオーバーロードは、1の観測された出力を持ちます。ただし、そのオーバーロードは明示的であるため、暗黙的な変換は許可されません。コンパイラ/標準ライブラリの実装が標準に厳密に準拠していないようです。

C ++ 11より前の標準では、これは正しい形式です。当時は、ストリーム入力演算子のオーバーロードを持つstd::cout暗黙的な変換演算子がvoid*ありました。ただし、その出力は異なります。std::coutオブジェクトのメモリアドレスを出力します。


11

投稿されたコードは、C ++ 11(またはそれ以降の準拠コンパイラ)用にコンパイルするべきではありませんが、C ++ 11以前の実装では警告なしでコンパイルする必要があります。

違いは、C ++ 11がストリームをブール値に明示的に変換したことです。

C.2.15節27:入出力ライブラリ[diff.cpp03.input.output] 27.7.2.1.3、27.7.3.4、27.5.5.4

変更:既存のブール変換演算子での明示的使用の指定
根拠:意図を明確にし、回避策を回避します。
元の機能への影響:暗黙的なブール変換に依存する有効なC ++ 2003コードは、この国際標準でコンパイルできません。このような変換は、次の条件で発生します。

  • bool型の引数を取る関数に値を渡す。
    ...

ostream演算子<<はboolパラメーターで定義されています。boolへの変換が存在する(明示的ではなかった)cout << coutため、C ++ 11より前のバージョンに変換されたため、cout << true1が生成されました。

C.2.15によると、これはC ++ 11以降ではコンパイルできません。


3
boolC ++ 03には存在する変換はありませんstd::basic_ios::operator void*()が、条件式またはループの制御式として意味があるものはあります。
Ben Voigt 2017年

7

この方法でコードを簡単にデバッグできます。使用時にcout出力はバッファリングされるため、次のように分析できます。

の最初の出現がcoutバッファを表し、演算子がバッファ<<の最後に追加することを想像してください。<<あなたの場合、演算子の結果は出力ストリームcoutです。あなたはから始めます:

cout << "2+3 = " << cout << 2 + 3 << endl;

上記のルールを適用すると、次のような一連のアクションが得られます。

buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

前に言ったように、結果buffer.append()はバッファです。最初はバッファが空で、次のステートメントを処理する必要があります。

ステートメント: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

バッファ: empty

まずbuffer.append("2+3 = ")、与えられた文字列を直接バッファに入れてになりbufferます。これで、状態は次のようになります。

ステートメント: buffer.append(cout).append(2 + 3).append(endl);

バッファ: 2+3 = 

その後、ステートメントの分析を続けcout、引数としてバッファーの最後に追加することに出会います。cout扱われ1ますが追加されますので1、あなたのバッファの最後に。今、あなたはこの状態にあります:

ステートメント: buffer.append(2 + 3).append(endl);

バッファ: 2+3 = 1

次にバッファにあるのは2 + 3、加算は出力演算子よりも優先順位が高いため、最初にこれらの2つの数値を加算してから、結果をバッファに格納します。その後、あなたは得る:

ステートメント: buffer.append(endl);

バッファ: 2+3 = 15

最後endlに、バッファーの最後にの値を追加すると、次のようになります。

ステートメント:

バッファ: 2+3 = 15\n

このプロセスの後、バッファーからの文字がバッファーから標準出力に1つずつ出力されます。したがって、コードの結果は2+3 = 15です。あなたはこれを見ている場合は、追加取得1からcout印刷してみました。<< coutステートメントから削除すると、目的の出力が得られます。


6
これはすべて真実です(そして美しい形式になっています)が、疑問を投げかけています。質問は「なぜそもそもcout << cout農産物が生産さ1れるのか」ということになると思います。、そしてあなたはそれが挿入演算子の連鎖についての議論の最中にそれを行うと主張したところです。
役に立たない

1
ただし、美しい書式設定には+1を使用します。これがあなたの最初の答えであることを考えると、あなたが助けようとしているのは素晴らしいことです:)
gldraphael
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.