C++での三項演算子(?: 条件演算子)は左辺値として使える

何気なく使っている三項演算子の闇が実は深かったのでメモ

# 三項演算子を左辺値として使う
三項演算子は基本的に右辺値でしか使わないイメージですが、実は左辺値としても使えるようです。
左辺値なので値を代入できます。

```cpp:test2.cpp
#include <stdio.h>

int main(int argc, char const* argv[])
{
    int i, j, b;

    i = j = 0;
    b = 0;

    (b ? i : j) = 10;

    printf("i = %d, j = %d\n", i, j);

    i = j = 0;
    b = 1;

    (b ? i : j) = 10;

    printf("i = %d, j = %d\n", i, j);

    return 0;
}
```

```bash:実行結果
$ g++ test2.cpp 
$ ./a.out 
i = 0, j = 10
i = 10, j = 0
```

なんか文法に違和感がありますがちゃんと動作しますね・・・。

## 補足1 : Cの場合はエラーが発生する?
Cの場合はエラーが発生しました、左辺値として定義されるのはC++だけなんでしょうか?

```shell-session:Cでコンパイルしたの場合のエラー
test2.c: In function ‘main’:
test2.c:10:17: error: lvalue required as left operand of assignment
     (b ? i : j) = 10;
                 ^
test2.c:17:17: error: lvalue required as left operand of assignment
     (b ? i : j) = 10;
```

追記:
@yohhoy さんに補足1についてコメントいただきました。言語仕様で決まってるんですね・・・
[条件演算子と左辺値の扱いの差 - yohhoyの日記](http://d.hatena.ne.jp/yohhoy/20121119/p1)

## 補足2 : アセンブラで見てみる

以下のfunc1とfunc2はアセンブラコード上では等価になるようです。

```cpp:test3.cpp
int i = 0, j = 0, s = 0;

void func1() {
    (s ? i : j) = 10;
}

void func2() {
    if (s) {
        i = 10;
    } else {
        j = 10;
    }
}
```

`gcc -S test3.cpp` を実行

```asm:test3.s
	.file	"test3.cpp"
	.globl	i
	.bss
	.align 4
	.type	i, @object
	.size	i, 4
i:
	.zero	4
	.globl	j
	.align 4
	.type	j, @object
	.size	j, 4
j:
	.zero	4
	.globl	s
	.align 4
	.type	s, @object
	.size	s, 4
s:
	.zero	4
	.text
	.globl	_Z5func1v
	.type	_Z5func1v, @function
_Z5func1v:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	s(%rip), %eax
	testl	%eax, %eax
	je	.L2
	movl	$10, i(%rip)
	jmp	.L1
.L2:
	movl	$10, j(%rip)
.L1:
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	_Z5func1v, .-_Z5func1v
	.globl	_Z5func2v
	.type	_Z5func2v, @function
_Z5func2v:
.LFB1:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	s(%rip), %eax
	testl	%eax, %eax
	je	.L5
	movl	$10, i(%rip)
	jmp	.L4
.L5:
	movl	$10, j(%rip)
.L4:
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	_Z5func2v, .-_Z5func2v
	.ident	"GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
	.section	.note.GNU-stack,"",@progbits
```

## 補足3 : メンバ関数もそのまま呼べる
~~左辺値なので~~ドット演算子を使ってメンバ関数もちゃんと呼び出せるっぽいです

```cpp:test4.cpp
#include <iostream>
#include <vector>

void func(bool s) {
    std::vector<int> a = {1, 2, 3};
    std::vector<int> b = {4, 5, 6};

    (s ? a : b).push_back(100);

    std::cout << __func__ << " : " << std::boolalpha << s << std::endl;

    for (auto n : (s ? a : b)) {
        std::cout << n << std::endl;
    }
}

int main(int argc, char const* argv[])
{
    func(true);

    func(false);

    return 0;
}
```

```shell-session:実行結果
$ g++ -std=c++11 test4.cpp && ./a.out
func : true
1
2
3
100
func : false
4
5
6
100
```

闇が深い・・・。(でも意外と便利かもしれない・・・？)

追記:
左辺値なのでと微妙な表現だったので型をちゃんと見ました。

```cpp:条件演算子の型を確認するテスト
#include <iostream>
#include <vector>
#include <typeinfo>

void func(bool s) {
    std::vector<int> a = {1, 2, 3};
    std::vector<int> b = {4, 5, 6};

    std::cout << "a           = " << typeid(a).name() << std::endl;
    std::cout << "b           = " << typeid(b).name() << std::endl;
    std::cout << "(s ? a : b) = " << typeid((s ? a : b)).name() << std::endl;
}

void func2(bool s) {
    std::cout << "(s ? rvalue : rvalue) = "
        << typeid((s ? std::vector<int>() : std::vector<int>())).name() << std::endl;

    (s ? std::vector<int>() : std::vector<int>()).push_back(100);
}

int main(int argc, char const* argv[])
{
    func(true);

    func2(true);

    return 0;
}
```
```shell-session:実行結果
$ g++ -std=c++11 test4.cpp && ./a.out
a           = St6vectorIiSaIiEE
b           = St6vectorIiSaIiEE
(s ? a : b) = St6vectorIiSaIiEE
(s ? rvalue : rvalue) = St6vectorIiSaIiEE
```

今回の `(s ? a : b)` は `std::vector<int>` の型を返す式になるので、そのメンバ関数も呼び出せるということですね。
@yohhoy さんにコメントでご指摘いただいたように、右辺値でもメンバ関数は呼び出せるようです。

# 参考
[今すぐ使える C++ コーディングテクニック集 - torus711 のアレ](http://torus711.hatenablog.com/entry/20131205/p1)
[C++ の、スマートだと思う人がいるかもしれないコードの書き方12選のつもりが6個ぐらいで断念 - Qiita](http://qiita.com/Nabetani/items/9d0fa9e750310d1be24e)
[ちひろのページ - 左辺値に三項演算子](http://chihiro718.jpn.org/JPN/etc/program/leftope.html)