LoginSignup
9
2

More than 5 years have passed since last update.

意外にもlvalueを結果とする演算子(ふっしぎー)

Last updated at Posted at 2017-03-20

まえおき

筆者はC90からC++14にワープしてきた、C++自体の勉強をはじめて間もない、しがない者です。間違いを含む可能性が大いにありますので、詳細については規格票を参照下さい(ちなみに僕が参照しているドラフトはN4296です)。

何卒、何卒、宜しくお願い申し上げます。

環境

$ uname -a
Linux ubuntu 4.4.0-53-generic #74-Ubuntu SMP Fri Dec 2 15:59:10 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.1 LTS
Release:        16.04
Codename:       xenial
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
コンパイルオプション(C90)
$ gcc -std=c90 -pedantic -Wall -o sample sample.c
コンパイルオプション(C++14)
$ g++ -std=c++14 -pedantic -Wall -o sample sample.cpp

では本題

C90では、評価結果が左辺値となる演算子は単項*[].->の4種類ぐらいだったように記憶しています。ですので、以下のコード(右辺値に代入を試みる)はコンパイルすると全滅します。

×C90ではill-formed
#include <stdio.h>

int main(void)
{
    int i,j,k,l,m;

    (1 ? i : j) = 11; /* NG: lvalue required as left operand of assignment */
    (i, j) = 22;      /* NG: lvalue required as left operand of assignment */
    (k = 0) = 33;     /* NG: lvalue required as left operand of assignment */
    l = 0;
    (l+=0) = 44;      /* NG: lvalue required as left operand of assignment */
    m = 0;
    ++m = 55;         /* NG: lvalue required as left operand of assignment */

    return 0;
}

条件演算子・コンマ演算子・代入演算子の式を代入先に指定するには、ポインタを介して無理矢理左辺値に変換する必要がありました。

○well-formed
#include <stdio.h>

int main(void)
{
    int i, j, k, l, m;

    *(1 ? &i : &j) = 11; /* OK */
    *(i, &j) = 22;       /* OK */
    *(k = 0, &k) = 33;   /* OK */
    l = 0;
    *(l+=0, &l) = 44;    /* OK */
    m = 0;
    *(++m, &m) = 55;     /* OK */

    printf("%d %d %d %d %d\n", i, j, k, l, m); 
    /* => 11 22 33 44 55 */

    return 0;
}

C++14では...

しかしC++14では、これらの演算子の評価結果のvalue categoryがlvalueとなりうることを知り、仰天しました。

○well-formed
#include <iostream>
#include <cstdio>

int main()
{
    int i,j,k,l,m,n;

    (true?i:j) = 11; 
    (i,j) = 22; 
    (k=0) = 33; 
    l = 0;
    (l+=0) = 44; 
    m = 0;
    ++m = 55; 
    n = 0;
    --n = 66; 

    printf("%d %d %d %d %d %d\n", i, j, k, l, m, n); 
    /* => 11 22 33 44 55 66 */
    return 0;
} 

すごいですね。「なりうる」と書いたように、もちろん、コンマ演算子の結果のvalue categoryは右オペランドのvalue categoryとなるなど条件はあるみたいですが。

なお、後置++/--の評価結果のvalue categoryはcv-unqualifiedのprvalueとなるため、このような代入はできないようです(cf.N4296 §5.2.6 Increment and decrement)。

特に、条件演算子の評価結果のvalue categoryを決定するプロセスは非常に複雑みたいですね。C90も複雑気味でしたが、C++14ほどではないです。

副作用完了の順序性は大丈夫なの?

とは、やはり個人的に気になったところです。C90では副作用完了点のひとつは式文中の完全式の終わりですが、C++ではどうか?(以下、N4296より引用。太字引用者)

5.18 Assignment and compound assignment operators
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation.

式文++m = 55;のvalue computationの順序関係はこうなるんじゃないかなという僕の考えを示します。

  1. 左/右オペランドのvalue computationが行われる(式++mは式m+=1、すなわち式m=m+1と等価なので、この式の各オペランドに対してもvalue computationが行われるが、煩雑になるので省略します)。
  2. ++mが計算された結果として、mへのlvalue参照を返す。
  3. 代入(更新後のmへ55を代入)が行われる。
  4. 代入式のvalue computationが行われ、mへのlvalue参照を返す。

となるので、部分式++mにともなう副作用は、代入時点ですでに完了していることが保証されているようです。

(あ、でも規格票の読込みがまだ甘いので、解釈が間違っていたらすみません...)

以上です。

9
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2