C++のcoutとか追加機能のデモだったんじゃ

C++で出てくる cout << x << y << endl のような構文は初見で不気味がられるものではないかと思うけど、実際のところ C を元に C++ が生まれた時に追加された機能の集大成としてデモンストレーションの意味もあったのでは、という考察というか感想。
9
angel (as ㌵㌤の猫) @angel_p_57

そこは「おまじない」で済ますところなんじゃないかなあ…と思いつつ、多分C++が出てきた当時も「なんでそんなんにしてるの」と思ってる人は多かったと思う。 ※多分、Cに追加した機能のフル活用というかデモンストレーション的な意味があったんだと思うけど twitter.com/Hayao0819/stat…

2024-01-04 11:06:12
山田ハヤオ @Hayao0819

俺C++のHello Worldで ・coutは何者 ・<<ってどういう意味 ・なんで関数じゃなくてシフト演算子のオーバーロードなんだ ということに囚われてしまいC++挫折した思い出がある twitter.com/hassyX/status/…

2024-01-04 02:42:34
angel (as ㌵㌤の猫) @angel_p_57

これ「デモンストレーション」と思ってるのは、あくまで自分の感想であって、実際に設計意図とかを見たわけではないんだけど。なぜ? とかどこらへんが? って言っておくとよかったりするかな? twitter.com/angel_p_57/sta…

2024-01-04 13:46:08

ということでつらつら書いてみた

angel (as ㌵㌤の猫) @angel_p_57

まず前提として、C では入出力処理を FILE型 ( typedefでFILEという名前になってる何らかの構造体 ) のデータを通じて、各種標準ライブラリの関数で行うことになっていたって話があって。

2024-01-04 13:47:09
angel (as ㌵㌤の猫) @angel_p_57

ちなみに、C においては標準出力は stdout という ( ポインタを通じて操作する )「標準で用意された」FILE型オブジェクト。自前で fopen しなくても標準で用意されてるから「どこに出力されるか」考えなくても使える存在

2024-01-04 13:48:11

標準出力を「処理」だとか「画面/コンソール」だと思ってる人が多いので念のための補足
※「stdoutを通じて出力される/たデータ」のことを「標準出力」という言い方は、まあないではないけど

angel (as ㌵㌤の猫) @angel_p_57

で、C で問題になるというか、事故の元になるのは「出力関数 printf/fprintf がデータの文字列化機能も兼用している」上に、複数のデータ型に対応するために「書式文字列」を必要とすること。

2024-01-04 13:48:41
angel (as ㌵㌤の猫) @angel_p_57

だから int ( 整数 ) の n なら fprintf(stdout, "%d", n) double ( 浮動小数点数 ) の d なら fprintf(stdout, "%f", d) とか、柔軟に対応はできるものの「型」の恩恵には与れてないって面もある。

2024-01-04 13:48:57
angel (as ㌵㌤の猫) @angel_p_57

早い話「どんなデータが来てるか言語環境側で分かってんのに、なんでわざわざ書式も一緒に指定せんといかんのじゃ」と。加えて書式指定のミスというリスクもある。

2024-01-04 13:49:24
angel (as ㌵㌤の猫) @angel_p_57

なのでここで出てくるのが追加機能の「関数多重定義」 これは、同じ名前の関数を異なる引数型のパターンで作れる、裏を返せば「型に応じて適切な関数を自動で選んでもらえる」機能になっている。

2024-01-04 13:49:51
angel (as ㌵㌤の猫) @angel_p_57

だから例えば myprint(stdout, n) なら fprintf(stdout, "%d", n) を、myprint(stdout, d) なら fprintf(stdout, "%f", d) を、というように内部処理の異なる複数バージョンの myprint を実装することができ、API利用者は型の違いを気にせずに済む。

2024-01-04 13:50:31
angel (as ㌵㌤の猫) @angel_p_57

ただ逆に、この myprint みたいなAPIを使わせようと思うと、以前は fprintf(stdout, "%d%d%d", i, j, k) みたいに一括で出力できていたものを myprint(stdout, i); myprint(stdout, j); myprint(stdout, k) と分割しなければいけない。これはダルい。

2024-01-04 13:50:45
angel (as ㌵㌤の猫) @angel_p_57

そこで出てくるのが追加機能の「演算子多重定義」 C では演算子の機能は標準で決まっているものから動かせなかったところ、C++ ではかなり自由に自分で定義することができる。これを使えば処理の呼び出しを簡単に書ける。( でもあんまりやると混乱の元でもある )

2024-01-04 13:51:05
angel (as ㌵㌤の猫) @angel_p_57

そこで出力っぽい演算子として選ばれたのが << と。まあ、他に使えそうなものがなかったのは分かるし、「データを送り出す」っぽいふいんきがあることは否定しない。否定しないんだけど…個人的にはやっぱり違和感がいつまでも拭えない。

2024-01-04 13:51:27
angel (as ㌵㌤の猫) @angel_p_57

まあそれでも、FILE型の代わりに新設した stream系 のクラスを使って、標準出力も cout の方を使うとして、cout << i; cout << j; cout << k と書けば myprint(stdout, i)… よりは簡単にはなっている。

2024-01-04 13:52:05

C++でのコード例で std:: はつけていない ( 原初のC++だと実際 namespace自体なかったし ) けど、今は std::cout のようにつける必要があるのでそこは注意。
※using namespace std はやめてね、ということで

angel (as ㌵㌤の猫) @angel_p_57

加えて、<< の実装を一工夫して「streamをreturnする」ということにすれば、cout << を何度も書かなくて済むようになる。

2024-01-04 13:52:20
angel (as ㌵㌤の猫) @angel_p_57

どういうことかというと、operator<<(stream, data){ 出力処理; return stream; } みたいな実装にしておく。で、cout << i << j << k というのは、これは ( ( cout << i ) << j ) << k のような処理的な結合になっているんだけど、

2024-01-04 13:52:42
angel (as ㌵㌤の猫) @angel_p_57

cout << i で i の出力処理が行われて cout 自身が return されるので、それを使って次の << j が処理される、つまり cout << j が処理されるのと一緒、と。次の k についても同様。 …ということで短縮したような書き方ができているということ。

2024-01-04 13:53:19
angel (as ㌵㌤の猫) @angel_p_57

その代わり、printf/fprintf だと return されるのが「出力した文字数」で、それを有効活用できるんだけど、「streamをreturn」だとそういう使い方はできなくなっている。代替機能あるのかは分からない。

2024-01-04 13:53:45
angel (as ㌵㌤の猫) @angel_p_57

ただ、「returnした値によって成功/失敗を判定する」という意味合いもあったところは、追加機能の「例外」で対処できるから、C の頃のような返り値の使い方には拘らなくなったんだろうな、という気はする。

2024-01-04 13:54:02
angel (as ㌵㌤の猫) @angel_p_57

こうして、初めて見ると意味不明な cout << i << j << k のような表現が爆誕してるんだけど、こう順を追って見ると、なるほどよくできている、と。…思えるんじゃないかな。…多分。

2024-01-04 13:54:18
angel (as ㌵㌤の猫) @angel_p_57

ところで「多重定義」って実際かなり強力で、自前のデータ型を struct なり class で作った場合、「C の printf の書式を独自に拡張する」なんて無理があるところ、多重定義なら自分で << の実装を追加で書くだけで簡単にできてしまう。

2024-01-04 13:54:51
angel (as ㌵㌤の猫) @angel_p_57

しかも、「データを出力する」という枠を越えて、ストリームを操作する色々な処理を << に任せることもできる。典型的なのは、C だと fflush(stdout) と別関数になっていたストリームのフラッシュ、これが cout << flush で書ける ( 改行付きの endl の方が有名だろうけど )

2024-01-04 13:55:16
angel (as ㌵㌤の猫) @angel_p_57

ちなみに「printfであった桁数指定とか柔軟な書式、なくなっちゃったんじゃ?」と気になった人もいるかもしれないけど、例えば桁数指定を cout << setw(5) << i と挟むことで fprintf(stdout, "%5d", i) 相当ができたり。カバーはされている。

2024-01-04 13:55:36