LoginSignup
17
15

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-12-19

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

三項演算子を左辺値として使う

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

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;
}
実行結果
$ g++ test2.cpp 
$ ./a.out 
i = 0, j = 10
i = 10, j = 0

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

補足1 : Cの場合はエラーが発生する?

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

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の日記

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

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

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 を実行

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 : メンバ関数もそのまま呼べる

左辺値なのでドット演算子を使ってメンバ関数もちゃんと呼び出せるっぽいです

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;
}
実行結果
$ g++ -std=c++11 test4.cpp && ./a.out
func : true
1
2
3
100
func : false
4
5
6
100

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

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

条件演算子の型を確認するテスト
#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;
}
実行結果
$ 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 のアレ
C++ の、スマートだと思う人がいるかもしれないコードの書き方12選のつもりが6個ぐらいで断念 - Qiita
ちひろのページ - 左辺値に三項演算子

17
15
3

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
17
15