実践C++入門講座こんにちは。C++を含む幾つかのプログラミング言語は左辺値や右辺値という概念を持ちます。これらはC++の型システムを使って安全性の高い(できるだけコンパイラにバグを検出させる)プログラムを書く時にたいへん有用です。簡単なようで混乱し勝ちな概念ですが、演算子によって左辺値・右辺値は決まってます。そこを理解すれば簡単ですので演算子との関係に着眼して解説します。1.古き良き?時代の左辺値と右辺値プログラミング言語の多くは、y=5*x+100と書いた場合、「5*x+100を計算してその結果をyへ代入しろ」という命令になります。C++も例外ではありません。左辺(この場合y)には変数を書かないといけません。右辺(この場合5*x+100)には色々なものが書けます。定数(例えば100)や変数(例えばx)、式(例えば2.8*x+3.14)などを書けます。この左辺に書けるもののことを「左辺値」(英語ではlvalue)、右辺に書けるもののことを「右辺値」(同rvalue)と呼びます。古き良き?時代:2011年前までは左辺値と右辺値の定義は単純でした。2011年に規定されたC++11標準規格にて拡張され、高速なプログラムをよりスマートに記述できるようになりました。理解するための難易度も相応に高いです。そこで、まだ単純だった時代の左辺値・右辺値を解説します。今後、学習が進むに連れてC++11の拡張の解説も進めます。左辺値は代入先のことですのでデータの容器です。右辺値はその中身のデータそのものです。y=100;とint型変数yが定義されていた場合、変数yが容器でありメモリが割り当てられています。その中身の100は変数yの中に記録さているデータです。右辺が100ではなく5*x+100のような式だった場合、データはどうなるかといいますと、式5*x+100を計算した結果です。5*x+100=yのような書き方はできません。右辺値を左辺に書くことは許されていません。間違って書くとコンパイル・エラーになります。「値」に何か代入するって意味をなしません。逆に、y=xのように左辺値を右辺に書くことはできます。これは、「xの中身を取り出してyへコピーしろ」と言う命令になります。左辺値には必ず中身のデータが入っています。その中身を取り出せば右辺値です。左辺値は右辺値へ常に変換できるのです。下記のような特徴があります。容器(変数)の場所を移動することはできません。容器(変数)を確保した時点でアドレスが決まり、使用するメモリも確定します。そのメモリに別のアドレスを割り当てることはできません。(*1)つまり、一度確保した容器(変数)を中身ごと移動することはできません。「移動」に近いことをする時は、別の場所に容器(変数)を確保して中身をコピーし、元の容器(変数)を解放する(他の用途に使って良いと許可する)ことになります。中身(値)はコピーできます。ある容器(変数)の中身(値)を取り出して別の容器(変数)へコピーすることが可能です。現実世界では中身を取り出して他の容器へ「移動」することができますね。逆にコンピュータの世界では「移動」することはできません。コピーすることができるだけです。「移動」みたいなことをしたい時は、コピーした後、元の容器の中身を0や何か無効な値に書き換えます。(*1)メモリに別のアドレスを割り当てることはできませんごめんなさい。実は不可能ではありません。PCにはMMUと言うハードウェアが搭載されており、これを操作することで可能です。それはOSの役割です。OSに近い部分を開発している時以外はそのような場面に出会うことはありませんし、自由に移動できるわけでもありません。C++の機能でもありません。いえ、そんなことはありません。左辺値は容器(変数)です。右辺値はその中身(値)です。油断してはいけません。実は意外に難しいです。1+1の結果は2です。見ての通り2は変数(容器)ではなく値(中身)ですから、右辺値です。このように多くの場合、計算した結果は数値であり、値(中身)です。容器(変数)にはなりえません。C++には左辺値(容器)を返却する式があります。計算の結果、容器を返してくるものがあるのです。以下、そのあたりの混乱しがちな部分を含めて解説します。ここを乗り越えることに成功すれば、あなたは一皮むけます。普通に演算子と言えば、足し算や掛け算の記号の+や*等のことをイメージすると思います。C++では更に演算子の概念が拡張されています。代入しますが「代入演算子」と呼ばれる演算子です。そのような演算子は、オペランドと演算結果の型と左辺値・右辺値に着目して分類することができます。その分類にそって解説します。(この分類は当ブログ独自のものではありません。referenceの分類です。リンク先は機械翻訳です。元の英語版はここにあります。)また、C++は演算子オーバーロードという仕組みで演算子の機能を拡張できますが、ここではC++のコア言語に組み込まれた演算子について解説します。オーバーロードは複雑ですので後日適切なタイミングで解説します。ところで、当講座で使う用語を2つ定義させて下さい。「オペランド」は、演算対象のことです。a+bの時、+は演算子(オペレータ)でaとbがオペランドです。enum型のことを指します。enum型については後日解説します。)2-1.算術演算子とビット演算子オペランド、および、演算結果は数値型の「右辺値」です。それぞれ多数の型があります。厳密にいつどの型になるのか決められていますが、かなりややこしいので、ここではざっくり説明します。int型より精度が低いオペランドはint型へ拡張されます。(1.の拡張適用後)、より精度の高い方の型に合わせて計算されます。右辺値2-2.論理演算子オペランド、および、演算結果はbool型の「右辺値」です。右辺値2-3.比較演算子オペランドは数値型、enum型、ポインタ型、メンバ・ポインタ型の「右辺値」です。演算結果はbool型の「右辺値」です。2-2-1.オペランドが数値型、enum型、ポインタ型の演算子enum型とポインタについては後日解説しますが、ポインタについてはここで少し解説してますので参考にして下さい。右辺値なお、標準規格上は、オペランドがポインタ型の場合、aとbは同じ配列の要素とその配列の最後+1へのポインタの時のみ結果は定義されています。添字が小さい要素を指すポインタの方が小さいものとして比較されます。実装はポインタに入っているアドレスを単純に比較すれば足りますので、単純にアドレスの値を比較していると考えて良いです。異なる配列や構造体等について、通常はどちらがアドレスの小さい方に割り当てられるのか決まっていないことを理解しておいて下さい。2-2-2.オペランドがメンバ・ポインタ型の演算子メンバ・ポインタ型については後日解説します。メンバ・ポインタ型の比較演算子は==と!=のみ定義されています。2-4.代入演算子2-4-1.代入演算子は左辺値を返却します代入演算子は左辺と右辺があります。代表的な例はa=bです。左辺側オペランドは任意の型の「左辺値」です。右辺側オペランドは左辺側オペランドの型へ変換可能な「右辺値」です。結果は左辺側オペランドそのもので、これは「左辺値」です。cがint型だった時、a=b=c;、a=(b=c);、(a=b)=c;などと書くことができます。代入演算子は右から左へ処理されるとC++の標準規格で決められています。a=b=c;はb=c→a=bの順序で処理されます。a=(b=c);も同じ順序で処理されます。(a=b)=c;は、まず括弧の中が先に処理されますのでa=bが実行されます。a=bは「左辺値」としてのaを返却します。次にa=cが実行されることになります。0;}繰り返しになりますが、(b+c)=a;はできません。b+cは数値型の「右辺値」を返却します。右辺値は中身(値)であり、容器(変数)ではありませんので代入できません。(a=b)=cはできます。a=bが返却するのもは左辺値であり、容器(変数)だからです。2-4-2.複合演算子について=は「2-1.算術演算子とビット演算子」で述べた演算子と複合し、複合演算子になります。+=は=と+の複合です。