本記事は「C言語の条件演算子」、その一点のみを解説するものです。
なお、本記事における規定は「プログラム言語C JISX3010-1993 (ISO/IEC 9899:1990)」に準拠しています(一部を除く。後述)。
はじめに
条件演算子は以下の構文規則をとりますが
第1オペランド ? 第2オペランド : 第3オペランド
本記事では、第1~第3の各オペランドに要求される型と、結果の型について解説を試みていきます。
第1オペランドの型
第1オペランドにはスカラ型を要求します。つまり算術型かポインタ型のいずれかですね。
第2・第3オペランドの型と結果の型
第2・第3オペランドの条件がこれまた複雑なのです。規格票からそのまま引用しますと
- 両オペランドの型が算術型である。
- 両オペランドの型が適合する構造体型又は共用体型である。
- 両オペランドの型が
void
型である。- 両オペランドが適合する型の修飾版又は非修飾版へのポインタである。
- 一方のオペランドがポインタであり、かつ他方が空ポインタ定数である。
- 一方のオペランドがオブジェクト型又は不完全型へのポインタであり、かつ他方が
void
の修飾版又は非修飾版へのポインタである。
これを真正面から理解しようとすると発狂するので、大雑把に以下5つのケースに分類することで発狂を回避します。
# | 第2・第3オペランドの組合せ | 結果の型 |
---|---|---|
1 | 両方が算術型の場合 | 両方に通常の算術型変換適用後の型 |
2 | 両方が構造体型または共用体型の場合 | その型 |
3 | 両方がvoid の場合 |
void |
4 | 両方がポインタ型の場合(void * を含まない) |
両方の合成型 (両オペランドのポインタが指す型のCV修飾を合成) |
5 | 両方がポインタ型の場合 ( void * を含む) |
void * (両オペランドのポインタが指す型のCV修飾を合成) |
上に示した通り、第2・第3オペランドについては型の組合せ方に制約があり、結果の型は各組合せに依存します。
以降は、各ケースについて解説していきます。
両方が算術型の場合
算術型に関しては「適合する型」という制約がないため、例えば第2オペランドをint
、第3オペランドにfloat
を指定してもよいです。
ただし、最終的には結果の型を確定する必要があるため、(二項+
演算子と同様に)両オペランドについて通常の算術型変換が適用された型が結果の型となります。
両方が構造体型または共用体型の場合
結果の型は、両オペランドが構造体型ならば構造体型、両オペランドが共用体型ならば共用体型となります。ただし、互いに適合する型でなければなりません。
struct a{int x;} a_, a2_;
struct b{int x;} b_;
cond ? a_ : a2_; /* OK */
cond ? a_ : b_; /* NG */
両方がvoid型の場合
結果の型はvoid
型となります。用途は思いつきませんが、結果の値を使用せず、第2・第3オペランドで副作用を起こしたい時かなあ。
void side_effect_a(void){}
void side_effect_b(void){}
cond ? side_effect_a() : side_effect_b(); /* OK */
両方がポインタ型の場合(void *を含まない)
両方が互いに適合する型(またはCV修飾を除けば適合する型)へのポインタである場合、結果の型は、両オペランドのポインタが指す型のすべてのCV修飾をもつ型となります。
const int *con_int_p = NULL;
volatile int *vol_int_p = NULL;
const volatile int *con_vol_int_p;
/* const int * + volatile int * -> const volatile int *
のようにCV修飾が合成された型となるので、代入式の制約により
con_vol_int_pの型はCV修飾版でなければならない。 */
con_vol_int_p = (cond ? con_int_p : vol_int_p);
一方が空ポインタ定数の場合、結果の型は他方の型となります。
両方がポインタ型の場合(void *を含む)
ちょっとややこしいのはvoid *
が絡んでくるケースです。一方がvoid
の修飾版または非修飾版へのポインタである場合、結果の型は「両オペランドのポインタが指す型のCV修飾を合成したvoid *
」となります。
百聞は一見に如かず、以下に例を示します。
# | 一方の型 | 他方の型 | 結果の型 |
---|---|---|---|
1 | const int * | void * | const void * |
2 | int * | const void * | const void * |
3 | const int * | volatile void * | const volatile void * |
また、void
型へのポインタと、関数型へのポインタは共存できません(関数型はオブジェクト型または不完全型には含まれないため)。代入式にも同じ制約があるので、「関数ポインタとvoid *
は仲が悪い」と覚えておきたいです。
void *vp = NULL;
void f(void);
cond ? vp : f;
/* `warning: ISO C forbids conditional expr between ‘void *’
and function pointer [-Wpedantic]` */
おことわり
「結果の型は「両オペランドのポインタが指す型のCV修飾を合成した
void *
」となります」という点に関してです。実はC90規格では、
void *
が絡んだ場合の型修飾子の合成については触れられていません。以下に規格の該当箇所を引用します(太字引用者)。第2及び第3オペランドがともにポインタである場合、又は一方が空ポインタ定数かつ他方がポインタである場合、結果の型は両オペランドによって指される型のすべての型修飾子で修飾された型へのポインタとする。更に、両オペランドが適合する型へのポインタ又は適合する型の異なる修飾版へのポインタである場合、結果の型は合成型とする。すなわち、一方のオペランドが空ポインタ定数である場合、結果の型は他方のオペランドの型とする。そうでなければ、一方のオペランドはvoid又はvoidの修飾版へのポインタであり、この場合、他方のオペランドをvoidへのポインタに変換し、結果の型はこの型とする。
(「プログラム言語C JISX3010-1993 (ISO/IEC 9899:1990)」より引用)太字部分を文字通りに捉えると、もともと
void *
だった方のオペランドがCV修飾つきであり、かつ他方がCV修飾なしの型だった場合、そのCV修飾は破棄されてしまうことになります。例えば、片方がconst void *
で、他方がint *
である場合、結果の型はvoid *
となるわけです。このとき、条件式の評価結果が
int *
のオペランドの値であれば問題ないですが、const void *
のオペランドの値だった場合、ポインタ参照先の書き込み禁止制約(const
)が外れてしまうことになります。他のパターンではそれを考慮して「合成型とする」としてるのに、なんでこのパターンだけ?という点が不可解なのです。「Cリファレンスマニュアル」(参考文献)を当たったところ、本記事と同様の解釈をとっていました。本記事では、道理にかなう同書の解釈をとりましたが、そもそも私が規格を誤読している可能性も無きにしもあらずなので、この点に関しては「規格から逸脱しているかもしれない説明」であることをここに記しておきます。
参考文献
- 「プログラム言語C JISX3010-1993 (ISO/IEC 9899:1990)」
- 「S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル 第5版」(玉井浩 訳)