数学およびコンピュータプログラミングにおいて、可変引数関数とは、引数の数が不定である関数、すなわち可変個の引数を受け取る関数のことである。可変引数関数への対応は、プログラミング言語によって大きく異なる。
可変引数関数として自然に現れる数学的および論理的な演算は数多くあります。例えば、数値の加算や文字列などの連結は、任意の数のオペランドに適用できる演算と考えることができます(ただし、これらの場合、形式的には結合法則が適用されます)。
多くのプログラミング言語で可変引数関数として実装されている操作の一つに、出力フォーマットがあります。C言語の関数printfとCommon Lispの関数formatは、その代表的な例です。どちらも、出力のフォーマットを指定する引数を1つと、フォーマットする値を指定する引数をいくつでも受け取ることができます。
可変引数関数は、一部の言語で型安全性の問題を引き起こす可能性があります。たとえば、C の可変引数関数は、不用意に使用すると、フォーマット文字列攻撃printfとして知られるセキュリティホールのクラスを引き起こす可能性があります。この攻撃は、可変引数関数の言語サポートが型安全ではないために発生します。つまり、関数がスタックに配置された引数よりも多くの引数をスタックからポップしようとしてスタックを破損させ、予期しない動作を引き起こす可能性があります。このため、CERT 調整センターは、 C の可変引数関数を重大なセキュリティリスクとみなしています。[ 1 ]
関数型プログラミング言語では、可変引数は、関数とリスト/シーケンス/配列を引数として受け取り、そのリストで指定された引数で関数を呼び出すapply関数と相補的であると考えられます。これにより、可変個の引数が関数に渡されます。関数型言語Haskellでは、可変引数関数は型クラスの値を返すことで実装できます。インスタンスが最終的な戻り値であり、関数である場合、これにより任意の数の追加引数が可能になります。TTr(T t) => x -> tx
項書き換え研究における関連テーマは、ヘッジ、またはヘッジ変数と呼ばれます。[ 2 ]引数を持つ関数である可変引数とは異なり、ヘッジは引数のシーケンスそのものです。また、可変長ではない(例えば「引数を 4 つまでしか取らない」)という制約(例えば「引数を 4 つだけ取る」)を持つこともできるため、可変引数と呼ぶのは誤解を招く可能性があります。しかし、これらは同じ現象を指しており、表現が混同されることがあり、可変引数変数(ヘッジと同義)のような名前が付けられることがあります。変数という言葉の二重の意味と、関数型プログラミングと項書き換えにおける引数と変数の違いに注意してください。たとえば、項(関数)は 3 つの変数を持つことができ、そのうちの 1 つがヘッジであるため、項は 3 つ以上の引数を取ることができます(ヘッジが空でも構わない場合は 2 つ以上の引数を取ることができます)。
C言語で可変引数関数を移植可能に実装するには、標準<stdarg.h>ヘッダーファイルを使用します。古いヘッダーは、に置き換えられて非推奨に<varargs.h>なりました。C++では、ヘッダーファイルが使用されます。[ 3 ]<stdarg.h><cstdarg>
#include <stdarg.h> #include <stdio.h>double average ( int count , ...) { va_list ap ; double sum = 0 ;va_start ( ap , count ); // C23 より前: アドレスを取得するために最後の固定パラメータが必要for ( int j = 0 ; j < count ; ++ j ) { sum += va_arg ( ap , int ); // ap を次の引数にインクリメント} va_end ( ap );return sum / count ; }int main ( int argc , char * argv []) { printf ( "%f \n " , average ( 3 , 1 , 2 , 3 )); return 0 ; }これは、任意の数の引数の平均を計算します。関数は引数の数や型を知らないことに注意してください。上記の関数は、型が でありint、引数の数が最初の引数で渡されることを想定しています (これはよく使われる方法ですが、言語やコンパイラによって強制されるものではありません)。printf など、他のいくつかのケースでは、引数の数と型はフォーマット文字列から判断されます。どちらの場合も、正しい情報を提供するのはプログラマー次第です。(あるいは、パラメータリストの終わりを示すために、 やのような番兵値を使用することもできます。) 関数が想定するよりも少ない引数が渡された場合、または引数の型が間違っている場合、無効なメモリ領域を読み込むことになり、フォーマット文字列攻撃などの脆弱性につながる可能性があります。システムによっては、番兵として を使用した場合でも、このような問題が発生する可能性があります。または、正しいターゲット型の専用のヌルポインタを使用して、これらの問題を回避できます。NULLnullptrNULLnullptr
stdarg.hは型 を宣言し、、、、 のva_list4 つのマクロを定義します。 および の各呼び出しは、の対応する呼び出しと一致する必要があります。可変引数を扱う場合、関数は通常、マクロによって操作される型(の例では )の変数を宣言します。va_startva_argva_copyva_endva_startva_copyva_endva_listap
va_startva_listは、オブジェクトと関数の最後のパラメータ (省略記号の前のパラメータ。マクロはこの参照を使用して位置を特定します) への参照の2 つの引数を取ります。 C23では、2 番目の引数は不要になり、可変引数関数は省略記号の前に名前付きパラメータを必要としなくなります。[注 1 ] [ 5 ]またはva_listで使用するためにオブジェクトを初期化します。参照が正しくない場合 (たとえば、最後のパラメータとは異なるパラメータへの参照、またはまったく異なるオブジェクトへの参照)、コンパイラは通常警告を発行しますが、コンパイルが正常に完了するのを妨げることはありません。va_argva_copyva_argva_listこの関数は、事前に初期化されたオブジェクトと型記述子の2つの引数を取ります。次の変数引数に展開され、指定された型になります。この関数を連続して呼び出すことでva_arg、各変数引数を順番に処理できます。型が正しくない場合、または次の変数引数がない場合は、未定義の動作が発生します。va_end引数としてオブジェクトを1つ受け取りますva_list。これはクリーンアップの役割を果たします。たとえば、変数引数を複数回スキャンしたい場合は、プログラマーはva_listを呼び出してオブジェクトを再初期化しva_end、その後、va_start再度 を呼び出す必要があります。va_copyは 2 つの引数を取り、どちらもva_listオブジェクトです。 は、2 番目のオブジェクト (初期化されている必要があります) を最初のオブジェクトにクローンします。 「可変引数を複数回スキャンする」の例に戻ると、これはva_start最初のオブジェクトに対してを呼び出しva_list、次に を使用してva_copyそれを 2 番目のオブジェクトにクローンすることで実現できますva_list。va_argと最初のオブジェクトva_list(で破棄va_end) を使用して可変引数を最初にスキャンした後、プログラマーはva_argと 2 番目のオブジェクトを使用して可変引数を 2 回スキャンできますva_list。は、包含する関数が戻る前に、va_endクローンされたオブジェクトに対しても呼び出す必要があります。va_listC# では、可変引数関数はparamsキーワードを使用して記述します。引数には型を指定する必要がありますが、 はobject[]包括的な型として使用できます。呼び出し側では、引数を 1 つずつ列挙するか、必要な要素型を持つ既存の配列を渡すことができます。可変引数形式を使用することは、後者の構文糖衣です。
名前空間Wikipedia.Examples ;Systemを使用します。public class Program { static int Foo ( int a , int b , params int [] args ) { // a と b を無視して、args 内の整数の合計を返します。int sum = 0 ; foreach ( int i in args ) { sum += i ; } return sum ; } static void Main ( string [] args ) { Console . WriteLine ( Foo ( 1 , 2 )); // 0 Console . WriteLine ( Foo ( 1 , 2 , 3 , 10 , 20 )); // 33 int [] manyValues = [ 13 , 14 , 15 ]; Console . WriteLine ( Foo ( 1 , 2 , manyValues )); // 42 } }C++ の基本的な可変引数機能は、C のものとほぼ同じです。C ++26より前は、省略記号の前のカンマを省略できました。C++ では、名前付きパラメータを持たない可変引数関数(たとえば、) も使用できます。ただし、C++26 までは、関数の最後の固定パラメータの名前が必要だったため、これらの引数にアクセスする移植性の高い方法はありませんでした。C++26 以降、C++ は、C23 に準拠した 1 引数形式を採用し、関数に固定パラメータがない場合でも引数にアクセスできるようになりました。voidf(...);va_startva_start
可変引数テンプレート(パラメータパック)は、C++ の組み込み関数である折りたたみ式と組み合わせて使用することもできます。C++ には Java スタイルの非テンプレート可変引数がないため、可変引数テンプレートは C++ で可変引数関数/パラメータを扱う唯一の型安全な方法です。
import std ;template < typename ... Ts > void fooPrint ( Ts ... args ) { (( std :: print ( "{} " , args )), ...); }int main ( int argc , char * argv []) { fooPrint ( 1 , 3.14f ); // 1 3.14 fooPrint ( "Foo" , 'b' , true , nullptr ); // Foo b true nullptr }C++ のCERTコーディング標準では、誤用リスクが低いため、C スタイルの可変引数関数よりも C++ の可変引数テンプレート(パラメータパック)の使用を強く推奨しています。 [ 6 ]可変引数テンプレートは、Java スタイルの型安全な可変引数パラメータを実現する唯一の方法です。
TJava でパラメータを型( に類似)のみに制限するには、概念[ 7 ]または(変換可能であることを意図した型の場合)を使用できます。 [ 8 ]T...argsstd::same_as<T, U>std::convertible_to<From, To>
std :: same_asを使用します。// Java宣言と同等// <T> void fn(T... args) template < typename T > void fn ( same_as < T > auto ... args ) { // ... }Carbon はC++ との相互運用性を念頭に置いて設計された言語であり、可変引数、特にパック展開を提供します。[ 9 ]
// 任意の要素型を持つ任意の数のベクトルを受け取り、// ベクトルの i 番目の要素が入力ベクトルの i 番目の要素のタプルであるタプルのベクトルを返します。fn Zip [ ... each ElementType :! type ]( ... each vector : Vector ( each ElementType )) -> Vector (( ... each ElementType )) { ... var each iter : auto = each vector . Begin (); var result : Vector (( ... each ElementType )); while ( ... and each iter != each vector . End ()) { result . push_back (( ... each iter )); ... each iter ++ ; } return result ; }Fortran 90 改訂以降、Fortran の関数またはサブルーチンはオプション引数を受け入れることができます。[ 10 ]引数リストは固定されていますが、optional属性を持つ引数は関数/サブルーチン呼び出しで省略できます。組み込み関数をpresent()使用して、オプション引数の存在を検出できます。オプション引数は、引数リストのどこにでも出現できます。
プログラムテスト暗黙的なしreal :: x !> すべての引数が渡されます: call foo ( 1 , 2 , 3.0 , 4 , x ) !< 出力: 1 \ 2 \ 3.0 \ 4 \ 6.0 ("\" は改行を表します) !> 最後の 2 つの引数が省略されます: call foo ( 1 , 2 , 3.0 ) !< 出力: 1 \ 2 \ 3.0 !> 2 番目と 4 番目の引数が省略されます: !> 省略された引数の後に配置される引数は、キーワードとともに渡す必要があります: call foo ( 1 , c = 3.0 , e = x ) !< 出力: 1 \ 3.0 \ 6.0 !> あるいは、Fortran 2023 改訂版では .NIL が導入されました。擬似定数!> は、省略された引数を表すために、foo ( 1 , . NIL ., 3.0 , . NIL ., x ) !< を実行すると、1 \ 3.0 \ 6.0 が出力されます。含まれる!> サブルーチン foo() には 2 つの必須引数と 3 つのオプション引数があります。サブルーチンfoo ( a , b , c , d , e ) integer , intent ( in ) :: a integer , intent ( in ) , optional :: b real , intent ( in ) :: c integer , intent ( in ), optional :: d real , intent ( out ), optional :: e print * , a if ( present ( b )) print * , b print * , c if ( present ( d )) print * , d if ( present ( e )) then e = 2 * c print * , c end if end subroutine最終プログラムGoの可変引数関数は、任意の数の末尾引数で呼び出すことができます。[ 11 ]fmt.Printlnは一般的な可変引数関数で、包括的な型として空のインターフェースを使用します。
パッケージメインimport "fmt"// この可変引数関数は、任意の数の整数を引数として受け取ります。func sum ( nums ... int ) { fmt . Print ( "The sum of " , nums ) // これも可変引数関数です。total := 0 for _ , num := range nums { total += num } fmt . Println ( " is " , total ) // これも可変引数関数です。}func main () { // 可変引数関数は、通常の方法で個々の引数を指定して呼び出すことができます。sum ( 1 , 2 ) // "[1 2] の合計は 3 です" sum ( 1 , 2 , 3 ) // "[1 2 3] の合計は 6 です"// スライスに複数の引数がある場合は、次のように func(slice...) を使用して可変引数関数に適用します。nums := [] int { 1 , 2 , 3 , 4 } sum ( nums ... ) // "[1 2 3 4] の合計は 10 です" }出力:
[1 2]の合計は3です [1 2 3]の合計は6です [1 2 3 4] の合計は 10 です
C#と同様に、JavaObjectの型も包括的な型として利用できます。
Javaでは、省略記号表記を用いることで、パラメータを可変引数にすることができます。これは基本的に配列と同等ですが、配列としてラップする必要はありません。例えば、String... argsとString[] argsは実質的に同じです。
パッケージorg.wikipedia.examples ;public class Program { // 可変引数メソッドは、受け取った追加の引数を配列に格納します。// そのため、`printArgs` は実際には 1 つのパラメータを持つメソッドです。// 可変長の `String` 配列です。private static void printArgs ( String ... strings ) { for ( String s : strings ) { System . out . println ( s ); } }public static void main ( String [] args ) { printArgs ( "hello" ); // printArgs(new String[] {"hello"}) の省略形printArgs ( "hello" , "world" ); // printArgs(new String[] {"hello", "world"}) の省略形printArgs ( args ); // args 配列全体を出力します} }JavaScript は可変引数の型を気にしません。可変引数は で表され...、これは引数を配列にまとめる「レスト パラメータ」を表します。
function sum (... numbers ) { return numbers . reduce (( a , b ) => a + b , 0 ); }console.log ( sum ( 1 , 2 , 3 ) ); // 6 console.log ( sum ( 3 , 2 ) ); // 5 console.log ( sum ( ) ); // 0引数オブジェクトを使用して可変引数関数を作成することもできますが、これはfunctionキーワードで作成された関数でのみ使用できます。
function sum ( ) { return Array.prototype.reduce.call ( arguments , ( a , b ) = > a + b , 0 ) ; }console.log ( sum ( 1 , 2 , 3 ) ); // 6 console.log ( sum ( 3 , 2 ) ); // 5 console.log ( sum ( ) ); // 0なお、 TypeScriptでは、restパラメータは配列であるため、以下のように表記されます。
function sum ( ...numbers : number []) : number { return numbers . reduce (( a , b ) => a + b , 0 ); }TypeScriptでは、restパラメータに正確な数の要素(つまりタプル)を持たせるように強制することもできます。
// 正確に3つの数値を強制的に生成する関数function sumExactlyThree (... numbers : [ number , number , number ]) : number { return numbers . reduce (( a , b ) => a + b , 0 ); }// 少なくとも2つの数値を強制する関数sumAtLeastTwo (... numbers : [ number , number , ... number []]) : number { return numbers . reduce (( a , b ) => a + b , 0 ); }// 最初の引数を文字列に強制し、その後は関数log (... args : [ string , ... any []]) { // ... }の後に続く引数を文字列に強制しますLua関数は、キーワードを使用して他の値と同様に可変引数を他の関数returnに渡すことができます。テーブルは、Lua バージョン 5.2 以降[ 12 ]table.unpackまたは Lua 5.1 以前[ 13 ]unpackを使用して、可変引数関数に渡すことができます。可変引数を値としてテーブルを構築することで、可変引数をテーブルとして使用できます。
function sum (...) --...は可変長引数を指定しますlocal sum = 0 for _ , v in pairs ({...}) do --可変長引数を使用してテーブルを作成することは、標準値を使用してテーブルを作成することと同じですsum = sum + v end return sum endvalues = { 1 , 2 , 3 , 4 } sum ( 5 , table.unpack ( values )) -- 15 を返します。table.unpack は他の引数の後に記述する必要があります。そうしないと、すべての値が関数に渡されません。function add5 (...) return ... + 5 --これは可変長引数の誤った使用法であり、最初に指定された値のみを返しますendentries = {} function process_entries () local processed = {} for i , v in pairs ( entries ) do processed [ i ] = v -- プレースホルダー処理コードend return table.unpack ( processed ) -- すべてのエントリを可変長引数として使用できる形式で返しますendprint ( process_entries ()) -- print関数はすべての可変長引数を受け取り、改行で区切って標準出力に書き込みますPascalはISO規格 7185 (「標準 Pascal」) および 10206 (「拡張 Pascal」)によって標準化されています。Pascal のどちらの標準化形式も、特定の組み込みルーチン( /および/ 、さらにEP / ) を除いて、可変引数ルーチンをサポートしていません。readreadLnwritewriteLnreadStrwriteStr
それにもかかわらず、Pascal の方言では可変引数ルーチン に似たメカニズムが実装されています。Delphiは、最後の仮引数に関連付けられる可能性のあるデータ型を定義しています。ルーチン定義内では、は、バリアントレコードの配列です。[ 14 ]上記のデータ型のメンバーにより 、引数のデータ型を検査し、その後の適切な処理を行うことができます。Free Pascal コンパイラも Delphi の可変引数ルーチンをサポートしています。[ 15 ]arrayofconstarrayofconstarrayofTVarRecVTyperecord
しかし、この実装では技術的には単一の引数、つまり が必要ですarray。Pascal では配列が同質でなければならないという制約があります。この要件はバリアントレコードを利用することで回避されます。GNU Pascal では省略記号 ( ) を使用した実際の可変引数形式パラメータ仕様が定義されています...が、2022 年現在、これを使用する移植可能なメカニズムは定義されていません。[ 16 ]
GNU PascalとFreePascalの両方で、外部で宣言された関数は、省略記号()を使用して可変引数の仮パラメータ指定を使用できます...。
PHPは、引数に型が指定されていない限り、可変引数の型を気にしません。
function sum ( ... $nums ) : int { return array_sum ( $nums ); }echo sum ( 1 , 2 , 3 ); // 6そして、型付き可変引数:
function sum ( int ... $nums ) : int { return array_sum ( $nums ); }echo sum ( 1 , "a" , 3 ); // TypeError: sum() に渡される引数 2 は int 型である必要があります (PHP 7.3 以降)Pythonは可変引数の型を気にしません。
from typing import Anydef foo ( a : Any , b : Any , * args : * tuple [ Any , ... ]) -> None : print ( args ) # args はタプル (不変シーケンス) です。if __name__ == "__main__" : foo ( 1 , 2 ) # args = () foo ( 1 , 2 , 3 ) # arga = (3,) foo ( 1 , 2 , 3 , "hello" ) # args = (3, "hello")キーワード引数は辞書に格納できます。
from typing import Any , Unpackdef bar ( * args : * tuple [ Any , ... ], ** kwargs : Unpack [ dict [ str , Any ]]) -> Any : # 関数本体args型はであるが*tuple[Any, ...]、 時には[ 17 ]*argsで注釈が付けられることに注意してください 。typing.Any
Rakuでは、可変引数関数を作成するパラメータの種類はスラーピー配列パラメータと呼ばれ、次の3つのグループに分類されます。
これらのパラメータは単一のアスタリスク(* *)で宣言され、反復可能な要素の 1 層以上を解消することによって引数を平坦化します(つまり、イテラブル)。
sub foo ( $a , $b , * @args ) { say @args . perl ; } foo ( 1 , 2 ) # [] foo ( 1 , 2 , 3 ) # [3] foo ( 1 , 2 , 3 , "hello" ) # [3 "hello"] foo ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, 4, 5, 6]これらのパラメータは二重アスタリスク(*)で宣言され**、リスト内の反復可能な引数を平坦化せず、引数をほぼそのまま保持します。
sub bar ( $a , $b , ** @args ) { say @args . perl ; } bar ( 1 , 2 ); # [] bar ( 1 , 2 , 3 ); # [3] bar ( 1 , 2 , 3 , "hello" ); # [3 "hello"] bar ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]]これらのパラメータはプラス記号()で宣言され、 「単一引数ルール」+が適用されます。このルールは、コンテキストに基づいてスラーピー引数の処理方法を決定します。簡単に言うと、引数が1つだけ渡され、その引数が反復可能な場合、その引数を使用してスラーピーパラメータ配列を埋めます。それ以外の場合は、(つまり、フラット化されていないスラーピー)のように動作します。+@**@
sub zaz ( $a , $b , + @args ) { say @args . perl ; } zaz ( 1 , 2 ); # [] zaz ( 1 , 2 , 3 ); # [3] zaz ( 1 , 2 , 3 , "hello" ); # [3 "hello"] zaz ( 1 , 2 , [ 4 , 5 ]); # [4, 5]、単一の引数で配列が埋められるzaz ( 1 , 2 , 3 , [ 4 , 5 ]); # [3, [4, 5]]、**@ のように動作するzaz ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]]、**@ のように動作するRubyは可変引数の型を気にしません。
def foo ( * args ) print args endfoo ( 1 ) # `[1]=> nil` と出力されますfoo ( 1 , 2 ) # `[1, 2]=> nil` と出力されますRust は関数で可変引数をサポートしていません。代わりに、可変引数をサポートするマクロを使用します。[ 18 ]これが、可変引数を受け取ってフォーマットするマクロが関数ではなくマクロである理由ですprintln!。
macro_rules! calculate { // 単一の `eval` のパターン( eval $e : expr ) => {{ { let val : usize = $e ; // 型を整数に強制するprintln! ( "{} = {}" , stringify! { $e }, val ); } }};// 複数の `eval` を再帰的に分解する( eval $e : expr , $( eval $es : expr ), + ) => {{ calculate ! { eval $e } calculate ! { $( eval $es ), + } }}; }fn main () { calculate ! { \ eval 1 + 2 , eval 3 + 4 , eval ( 2 * 3 ) + 1 } }Rust は機能スイッチを介して C の可変引数システムとやり取りできますc_variadic。他の C インターフェースと同様に、このシステムはunsafeRust から見なされ、C リンケージでマークされます。[ 19 ]可変引数自体には注釈が付けられ、関数が可変引数にアクセスしないことが保証されている場合は、関数にマークを付けることができます。[ 20 ]...safe
#![feature(c_variadic)]pub unsafe extern "C" fn add ( n : usize , mut args : .. .) -> usize { let mut sum = 0 ; for _ in 0 .. n { sum += args . arg :: < usize > (); } sum }object Program { // 可変引数メソッドは、受け取った追加の引数を配列に格納します。// そのため、`printArgs` は実際には 1 つのパラメータを持つメソッドです。// 可変長の `String` 配列です。private def printArgs ( strings : String * ): Unit = { strings . foreach ( println ) }def main ( args : Array [ String ]): Unit = { printArgs ( "hello" ); // printArgs(["hello"]) の短縮形printArgs ( "hello" , "world" ); // printArgs(["hello", "world"]) の短縮形} }Swiftは可変引数の型を気にしますが、包括的なAny型も利用可能です。
func greet ( timeOfTheDay : String , names : String ...) { // ここでは、names は [String] ですprint ( "Looks like we have \( names.count ) people " ) for name in names { print ( "Hello \( name ) , good \( timeOfTheDay ) " ) } }greet ( timeOfTheDay : "morning" , names : "Joseph" , "Clara" , "William" , "Maria" )// 出力: // 4 人の人がいるようです// こんにちはジョセフ、おはようございます// こんにちはクララ、おはようございます// こんにちはウィリアム、おはようございます// こんにちはマリア、おはようございますTclのプロシージャまたはラムダは、最後の引数が の場合、可変引数になりますargs。これは、残りのすべての引数のリスト (空の場合もあります) を含みます。このパターンは、他の多くのプロシージャのようなメソッドでよく見られます。[ 21 ] [ 22 ]
proc greet { timeOfTheDay args } { puts "[llength $args] 人の人がいるようです"foreach name $args { puts "こんにちは、$nameさん、$timeOfTheDayさん、お元気ですか" } }「おはよう」「ジョセフ」「クララ」「ウィリアム」「マリア」と挨拶する# 出力: # 4人いるようです# こんにちはジョセフ、おはようございます# こんにちはクララ、おはようございます# こんにちはウィリアム、おはようございます# こんにちはマリア、おはようございます