可変長引数

引数が固定ではなく任意の個数となっている引数

可変長引数(かへんちょうひきすう、: variable length argumentsvariadic arguments)とはプログラミング言語において、関数(サブルーチンメソッドを含む)やマクロ引数が固定ではなく任意の個数となっている引数のことである。 可変引数可変個引数とも呼ばれる。

可変長引数を持つ関数を可変長引数関数: variadic function)と言う。

いくつかの言語では型安全が保証されなくなるので注意が必要である。

手法 編集

可変長引数を取る手続きあるいは関数メソッドは、多くのコンピュータプログラミング言語でサポートされている。

  • C言語 - 標準Cライブラリに用意されているstdarg.h英語版を使う。仮引数に型の指定は特に行わない。
  • C# - 配列型の仮引数の直前にparamsキーワードを指定する。
  • Java - 仮引数の型名と仮引数名の間に ... を記述する。実引数は配列に格納され、型には制限が付く。J2SE 5.0で追加された機能。Javaの文法#可変引数を参照。
  • JavaScript - argumentsオブジェクトを利用する。
  • Perl - サブルーチンに固定の仮引数を指定することはできない。どんなサブルーチンでも引数はリスト変数 @_ を通して動的に受け取る以外に方法がない。
  • Python - 仮引数名の前に*あるいは**を付けてアクセスする。
  • Common Lisp - &rest に続けてリストを受け取る仮引数名を指定する。
  • Scheme - . に続けてリストを受け取る仮引数名を指定する。

編集

C言語 編集

C言語においては printfscanf といった標準ライブラリの関数が可変長引数をとる代表例である。可変長引数をとる関数のユーザー定義も可能である。信頼できない入力を扱う場合は書式文字列攻撃に注意する必要がある。可変長引数として渡す際に型情報が失われるため、型に関する情報を別途渡すなどする必要がある。

倍精度浮動小数点数の平均値を求める関数average_of_doublesの例を考える。

#include <assert.h>
#include <stdarg.h>
/* stdarg.h には、マクロ va_start、va_arg、va_end が定義されている。対応する va_start と va_end の呼び出しは、同じ関数内でなければならない。 */

double average_of_doubles(int count, ...) {
  va_list ap;
  double sum = 0;
  int i;
  assert(count > 0);
  va_start(ap, count); /* スタック変数のアドレスを参照するため、最後の引数が必要である。 */
  for (i = 0; i < count; ++i) {
    /* スタックに積まれているはずの double 型の値を取り出し、ポインタ変数 ap が次の引数を指すように変更する。 */
    sum += va_arg(ap, double);
  }
  va_end(ap);
  return sum / count;
}

上記を呼び出すコード例は次の通りである。データの個数を最初の引数として与えている。

#include <stdio.h>

int main(void) {
  double average1 = average_of_doubles(3, 1.0, 2.0, 3.0);
  double average2 = average_of_doubles(4, 1.5, 2.1, -3.0, 0.7);
  printf("%f\n", average1);
  printf("%f\n", average2);
  return 0;
}

Cの可変長引数には、「既定の実引数拡張」(default argument promotion) と呼ばれる暗黙の型変換規則が適用される[注釈 1][注釈 2]。例えばfloat型の実引数は、double型に拡張される。関数形式マクロva_arg()を使ってスタックから値を取り出す際、指定した型の値が実際には格納されていなかった場合、未定義動作を引き起こす。

上記の例では、可変長引数にdouble型の値(または既定の実引数拡張を受けるfloat型の値)が渡されることを想定しているが、可変長引数に間違ってintなどの整数型の値を渡してもコンパイルは通ってしまい、未定義動作を引き起こす。この例では同じdouble型の1つ以上の値を渡すことができればよいので、可変長引数を使うのではなく、単純にdouble配列を使って型安全に実装すべきである。

double average_of_doubles(const double* values, int count) {
  double sum = 0;
  int i;
  assert(values);
  assert(count > 0);
  for (i = 0; i < count; ++i) {
    sum += values[i];
  }
  return sum / count;
}

Cプリプロセッサ・マクロ 編集

C言語のプリプロセッサのマクロ定義において、可変長引数マクロ[1]には、以前はトリック的な方法[2]が使われていたが、C99で本物の可変長引数マクロが標準化された。__VA_ARGS__という識別子を使用する。

次のような関数の呼び出しに展開されるマクロを定義したいとする。関数が可変長引数をとるため、機能を制限したくなければ、マクロも可変長引数をとるようにしたい。

void realdbgprintf(const char *sourceFilename, int sourceLineNumber, const char *formatString, ...);

C++での可変長引数マクロの設計に問題があるため[要説明]、以下に示すような定義は行えない。

#define dbgprintf(cformat, ...) realdbgprintf(__FILE__, __LINE__, cformat, __VA_ARGS__)

この書き方だと、dbgprintf("Hello")と記述した際にrealdbgprintf(__FILE__, __LINE__, "Hello", )と展開される。関数の引数リストをコンマで終えると構文エラーを起こすため、printfのような使い勝手は得られない。

gccの独自構文では##__VA_ARGS__という識別子もサポートしている。これを利用することにより引数がゼロ個のものも定義できる。

#define dbgprintf(cformat, ...) realdbgprintf(__FILE__, __LINE__, cformat, ##__VA_ARGS__)

gcc以外でも使用するためには__VA_ARGS__を使うほかないが、その場合は書式文字列も含めて可変長引数として渡す必要がある。

#define dbgprintf(...) realdbgprintf(__FILE__, __LINE__, __VA_ARGS__)

Microsoft Visual C++の独自拡張モードでは、ゼロ個の引数を受け取る__VA_ARGS__は末尾コンマを抑制する動作をする[3]

C++20では、空の可変引数を処理するための__VA_OPT__マクロをサポートする[4]

C++ 編集

可変長引数テンプレート[5]C++11でサポートされた。

template<typename... Args> class tuple;

このtupleクラステンプレートは、テンプレート引数としていくらでも型名を取れる:

tuple<std::vector<int>, std::map<std::string, std::vector<int>>> someTuple; // tuple インスタンス。
tuple<> emptyTuple;

std::tupleとしても標準化されている。

また、C++11ではC99との互換性向上のため、可変長引数マクロが標準化された (N1653)。

Java 編集

平均値を求める静的メソッド average の例を考える。平均値の計算には最小でも1個の値が必要であるから、固定個数の仮引数(必須の仮引数)として x0 を指定しており、それ以降を任意個数の引数としている。任意個数の値は仮引数 xsdouble 型の配列として格納される。

static double average(double x0, double ... xs) {
  double sum = x0;
  int count = 1;
  for (double x: xs) {
    sum += x;
    ++count;
  }
  return sum / count;
}

上記を呼び出すコード片の例は次の通りである。

System.out.println(average(0.3, 9.4, 5.2, 4.5, 9.7));

Scheme 編集

平均値を求める手続き average の例を考える。平均値の計算には最小でも1個の値が必要であるから、固定個数の仮引数(必須の仮引数)として x0 を指定しており、それ以降を任意個数の引数としている。任意個数の値は仮引数 xs にリストとして格納される。

(define (average x0 . xs)
  (let ((sum x0) (count 1))
    (for-each
      (lambda (x)
        (set! sum (+ sum x))
        (set! count (+ count 1)))
      xs)
    (/ sum count)))

上記を呼び出すコードの例は次の通りである。

(display (average 0.3 9.4 5.2 4.5 9.7)) (newline)

英語 編集

variadicは「可変長引数の」「可変個引数の」という意味の形容詞である。C言語などでは可変長引数のことを「variable argument」と呼ぶ。辞典などでは直訳して「変数引数」とされていることがあるが、これは誤訳である。なお、「変数引数」という言葉は、Pascalなどで変数渡しに使用される「variable parameter」の定訳であり、これは可変長引数とは違うものを指す。したがって、可変長引数のことを「変数引数」と呼ぶのは混乱の原因となる。

脚注 編集

注釈 編集

  1. ^ 例えばCの国際規格ISO/IEC 9899:1999またはその翻訳であるJIS X 3010:2003を参照のこと。
  2. ^ C++の国際規格ISO/IEC 14882:2003の翻訳であるJIS X 3014:2003では、「既定の実引数昇格」と呼ばれているが、同じものである。

出典 編集

  1. ^ : variadic macro
  2. ^ マクロ呼び出しにおいて余計なカッコを付けておくことで、カッコとコンマを含むような文字列そのものをマクロ引数に対応させる。
  3. ^ Variadic macros | Microsoft Docs
  4. ^ 可変引数が空でない場合のトークン置換 - cpprefjp C++日本語リファレンス
  5. ^ : variadic template

関連項目 編集