いろんな言語で可変長引数


関数の引数を任意の個数だけ渡せる機能は、多くの言語にありますが、それぞれ微妙に仕様が違っています。

C言語

C言語では、学習で最初に使うようなprintfも可変長の引数で構成されていますが、そういう関数を作ろうとすれば意外とやりづらいものです。

stdargs.hというファイルに、可変長引数用のマクロが用意されています。

  • va_start…引数ポインタの初期化
  • va_arg…引数の取得
  • va_end…引数ポインタの後始末

実装の特徴として、以下のことがあります。

  • va_startに、可変引数になる直前の引数を渡さないといけないので、引数0個で呼び出すことは不可能です。
  • 可変長引数の終わりをシステム側で知ることは不可能なので、「printfのように、固定の引数でどんな引数が続くか伝える」「(ポインタ引数が続くのであれば最後にNULLを入れるというように)引数の終わりを示す値を決めておく」などが必要になります。
  • (printfでは例外的にチェックしてくれることもありますが)コンパイラによる型チェックはありません。実際に渡した引数と違う型で取得しようとした場合、どんな動作をするかはわかりません。
  • 配列を展開して引数にするような、正当な方法もありません。

JavaScript

ES5まで

ES5までのJavaScriptの場合、可変長引数と銘打った特別な記法はありませんが、「仮引数と実引数で数が一致しなくても問題ない」ということもあって、可変長引数自体には対応できます。

JavaScriptの関数では、引数がargumentsという「配列のようなオブジェクト」に入っていて、arguments.lengthで引数の個数を取得できます。なお、仮引数があった場合に、仮引数と同じ位置のargumentsの要素が連動する、という挙動がありましたが、ES5のstrictモードでは廃止されています。

そのまま添字をforで回しても構いませんし、本当の配列に変換したければ、Array.prototype.slice.call(arguments)で可能です。

なお、配列の中身を引数リストに変換したい場合は、関数.apply(thisにするオブジェクト,配列)とすれば可能です。

ES6

ES6では、PHPと同じような...(spread operator)で、「可変長の引数を配列で受け取る」「引数に配列などを展開して渡す」ことが可能となっています。なお、ES6のArrow Function(=>を使った関数表記)では、arguments外側にあるものを引き継ぐので要注意です(MDN)。素直にspread operatorで書きましょう。

Ruby

Rubyは可変長引数を明示的にサポートしていますし、引数の数も厳密に取ります。仮引数の宣言の際に、*を前置させた引数があると、それが残りの引数を配列として受け取ります(性質上、引数リストに1つしか書けません)。

引数の数
def foo (arg1, *rest)
  # 1個以上の引数が必要
end

def bar (*args)
  # いくつでも問題なし
end

def baz (*args, last)
  #別に*付き引数は最後でなくてもいい
end

配列を展開して引数にしたい場合も、method(*arr)のように書くことで展開ができます。

PHP

PHPの場合、PHP 5.6以降では...という構文ができていて、Rubyでの*のように、可変長引数を自動で配列に詰めてくれます。そして、この引数リストにタイプヒントを付けることも可能です。

PHP 5.5以前の場合も、関数は仮引数以上の個数の引数を受け取ることができて、func_get_argsfunc_num_argsなどの関数で処理することができます。

引数として配列を展開したい場合、PHP 5.6以降では...を使うことができますし、それ以前でもcall_user_func_arrayを使えばできなくはないです。