プログラミング言語の変数宣言では、一つの宣言式で複数の変数を宣言することができる。
int a, b, c;
ただ、仮引数の宣言部ではこれができない。
// error: unknown type name 'b'
void fn(FILE a, b) {}
// warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
int add(int a, b) { return a + b; }
// <identifier> がありません。
public int f(int a, b) { return a + b; }
なぜだろうか。
技術的には可能なはずなのである。しかしそれをあえてやらない理由があるのだろう。
目次
必要性が無い
必要性の薄い軽微な機能であること、そして将来的な仕様拡張や仕様変更の際の柔軟性を担保する目的があると考えられる。
そもそも変数の宣言式と仮引数の宣言式は全く異なる目的や領域の上にあるため、両者に一貫性を求めること自体が無意味なのである。行き過ぎた一貫性の追求は柔軟性の排除に繋がる。
複数宣言は嫌われている
そもそもこの変数の複数宣言はプログラマの間では忌み嫌われている。
言語によっては一つの宣言に基本型やポインタ型、配列型を混在させることができてしまうためだ。
char character, *string, array[3];
初期値が絡めば更に読みづらく意図の掴みにくいコードが生まれてしまう。
また次の例も、誤解を招きやすいコードとして大変有名である。
char* a, b;
この場合の変数a
はchar *a
、つまりchar型のポインタ変数として宣言される。ただし、変数b
についてはポインタ変数ではなく、char b
つまり通常のchar型として宣言されてしまう。大変ややこしい。両者の具体的な違いは以下のページに書かれているので参考にされたい。
つまるところ、現代ではこの複数宣言はバッドノウハウとして扱われているのである。そのためわざわざこのような粗悪な機能を仮引数宣言へ取り入れる理由も無いのである。
C言語の互換性問題
ただC言語については過去の互換性も大きく関係している。
昔のC言語では型名を省略することができたため、現代のコンパイラでも以下のようなコードが普通にコンパイルできてしまう(もちろん警告は発生する)。
add(a, b) { return a + b; }
この場合は引数a
, b
は暗黙的にint
型として宣言される。add
関数の戻り値型も同様にint型となる。
ちなみに各識別子に対して型名を明示することもできる。
float add(a, b)
float a, b;
{ return a + b; }
これらはC言語仕様の負の遺産の一つとも言える。
そしてこれらの仕様を用いると、実は先程のint a, b
をコンパイルすることができてしまう。
int add(int a, b) { return a + b; }
この場合、仮引数b
はコンパイラの互換性によってint型の仮引数として認識されるようになる。
「なんだ、書けるじゃないか」
いいや、待って欲しい。以下のような場合には対応できない。
float add(float a, b) { return a + b; }
この場合の仮引数b
はfloat
型にはならない。やはりint型として認識されてしまう。
そのため仮に第二引数にfloatの値を渡してもintの値として切り捨てられてしまう。
add(0.1, 0.1); // 戻り値: 0.1(0.2ではない?!)
よって、C言語では関数宣言時の仮引数で、変数宣言時のような宣言方法(int a, b
)を利用することはできないし、この先もおそらく実現は難しいだろうと考えられる。なぜなら、下手に仮引数の挙動を規定してしまうと、過去の既存のコードに影響を与えてしまう恐れがあるためだ。冗談のように聞こえるかも知れないが、この世界は意外なほど互換性に厳しいのである。
保守的なC言語
C言語は過去の互換性を重視するあまり、言語としての柔軟性と将来性を失っているようにも思える。しかし同時に、C言語は過去の資産と互換性を維持することで、結果的に言語仕様の普遍性やシンプルさ、コンパクトさを保つことができているのである。これはC++言語とは対照的だ。
互換性という建前で国際機関から機能拡張を制限されているC言語。なんというか中二心をくすぐられるものがある。
俺「互換性という名の鎖から解き放たれた時、君は新たな次元へとシフトする」(キリッ!!)
C言語「うわ・・・なんか面倒くさいのが来た・・・」