C言語の条件演算子に関する雑記

条件演算子の中央オペランド(第2オペランド)がconditional-expressionでない理由

C11の規格(N1570)を見ると、条件演算子の文法はP467 (6.5.15)にて、以下のように記されている。 (文法はBNFで書き直したので、原文通りではない。)

conditional-expression : logical-OR-expression
                       | logical-OR-expression '?' expression ':' conditional-expression
                       ;
conditional-expression (6.5.15)

で、何故第2オペランドがconditional-expressionではなくexpressionなのかという話である。

assignment-expression : conditional-expression
                      | unary-expression assignment-operator assignment-expression
                      ;
expression : assignment-expression
           | expression ',' assignment-expression
           ;
assignment-expression (6.5.16), expression (6.5.17)

その答えは、第2オペランドに代入式を許可するためである。

1 ? a = 2 : 3;という文を考えてみる。 Cの定義通り第2オペランドがexpressionであれば、a = 2expressionとして処理される。

しかし、第2オペランドをconditional-expressionとしてしまうと、a = 2logical-OR-expressionとして解釈することはできず、代入式をlogical-OR-expressionとするには(a = 2)のようにparenで括らなくてはならない。

よって、括弧なしで第2オペランドに生の代入式を使うためには、conditional-expressionでなくexpressionを使わなければいけないのである。 (ちなみに、assignment-expressionでも代入式は直接使えるようになるが、コンマがparenなしで使えなくなる。)

C++における中央オペランド(第2オペランド)の文法

C++14の規格(N3797)においては、条件演算子の文法はCとは若干異なり、以下のように定義されている。

conditional-expression : logical-or-expression
                       | logical-or-expression '?' expression ':' assignment-expression
                       ;
assignment-expression : conditional-expression
                      | logical-or-expression assignment-operator initializer-clause
                      | throw-expression
                      ;
expression : assignment-expression
           | expression ',' assignment-expression
           ;
conditional-expression, assignment-expression, expression 抜粋(N3797 P1208より)

Cと異なり、条件演算子の第3オペランドがconditional-expressionではなくassignment-expressionとなっている。 この違いの影響は、Wikipediaで例示されている、e = a ? b : c = d;のような文において現れる。

Cのように第3オペランドがconditional-expressionであれば、e = ((a ? b : c) = d);のように解釈され、条件演算子が代入の左辺となる。 これはC言語においてはエラーである。 (一応、大昔にはこれを可能にするgcc拡張も存在した。後述。)

一方、C++のように第3オペランドがassignment-expressionであれば、e = (a ? b : (c = d));のように解釈される。 C++特有の機能を使っていないからと言って、拡張子を.cに直してしまうとコンパイルが通らなくなる例だ。

条件演算子による式を左辺値として使えるようにするgcc拡張

私の記憶では、gcc独自拡張によって、C言語でも条件演算子による式を左辺値として使えるはずだったのだが、wandbox(gcc-4.8.2)で試してみたところ、コンパイルが通らなかった。

不思議に思って調べてみた結果が以下の通りだ。

  • Lvalues - Using the GNU Compiler Collection (GCC)
    • gcc-3.4.6 (gcc-3の最後のバージョン、リリース)における、当該拡張のドキュメント。
    • Lvalues: Using `?:', `,' and casts in lvalues.
    • 条件演算子どころか、コンマやキャストによる式でさえも左辺値にできるという拡張である。
  • C Extensions - Using the GNU Compiler Collection (GCC)
    • gcc-4.0.0 (gcc-4の最初のバージョン、リリース)における、C拡張一覧のドキュメント。
    • Lvalues拡張が消えている

どうやら10年以上前に廃止された拡張だったらしい。 コンパイラ拡張をできるだけ使わないように生きてきたとはいえ、今日に至るまで全く気付かなかったのは結構ショックだ。