C ++の可変数の引数?


293

可変数の引数を受け入れる関数を作成するにはどうすればよいですか?これは可能ですか?


49
現時点では、C ++ 11では、この質問に対する答えは大きく異なります
K-ballo 2013年

1
K-BALLO @最近の質問が最近、これと同じ事を尋ねたと私はそれが閉じて正当化するために、この必要なものを感じたので、私もC ++ 11個の例を追加しましたstackoverflow.com/questions/16337459/...
Shafik Yaghmour

1
C ++ 11以前のオプションも私の回答に追加したので、利用可能な選択肢のほとんどをカバーできるようになりました。
Shafik Yaghmour 2013

@ K-ballo強制的な引数の型が必要な場合に備えて、C ++でそれを行う方法はありません。foo(int ... values)のような構成はありません。 C ++ 11での動作はすばらしい
グレイウルフ2016

回答:


152

おそらくすべきではありませんし、やりたいことをより安全で簡単な方法で行うことができます。技術的には、Cで可変数の引数を使用するには、stdarg.hをインクルードします。そこから、va_list型と、その型を操作する3つの関数va_start()va_arg()およびを取得しますva_end()

#include<stdarg.h>

int maxof(int n_args, ...)
{
    va_list ap;
    va_start(ap, n_args);
    int max = va_arg(ap, int);
    for(int i = 2; i <= n_args; i++) {
        int a = va_arg(ap, int);
        if(a > max) max = a;
    }
    va_end(ap);
    return max;
}

私に尋ねれば、これは混乱です。見た目が悪く、安全ではなく、概念的に達成しようとしていることとは何の関係もない技術的な詳細がたくさんあります。代わりに、オーバーロードまたは継承/ポリモーフィズム、ビルダーパターン(operator<<()ストリーム内など)、またはデフォルトの引数などの使用を検討してください。これらはすべて安全です。コンパイラーは、何をしようとしているのかを知ることができるため、停止する機会が多くなります。あなたがあなたの足を吹き飛ばす前に。


7
おそらく、varargs関数への参照を渡すことはできません。これは、コンパイラーが値渡しのタイミングと参照渡しのタイミングを知らないため、および基礎となるCマクロが参照の処理方法を必ずしも認識しないためです。プロモーションルールなどのために、可変引数でC関数に渡すことができます。
ジョナサンレフラー、

3
...構文の前に少なくとも1つの引数を指定する必要がありますか?
Lazer

3
@Lazerは言語やライブラリの要件ではありませんが、標準ライブラリではリストの長さを知る手段がありません。この情報を提供するか、どういうわけかそれを自分で理解するには、発信者が必要です。以下の場合にはprintf()、例えば、関数は、可変引数リストに期待するべきであるどのように多くの追加の引数を把握するために、特別なトークンの文字列引数を解析します。
wilhelmtell 2010年

11
おそらく<cstdarg>C ++の代わりに使用する必要があります<stdarg.h>
newacct

11
可変数の引数は、デバッグや、一部の配列を満たす関数/メソッドに最適です。また、max、min、sum、averageなどの多くの数学演算にも最適です。ごちゃごちゃしないと、ごちゃごちゃになりません。
トマーシュZato -復活モニカ

395

ではC ++ 11あなたに2つの新しいオプション、持っている可変引数関数の参照ページ代替セクションの状態を:

  • 可変テンプレートは、可変数の引数を取る関数を作成するためにも使用できます。これらは、引数のタイプに制限を課さず、整数および浮動小数点の昇格を実行せず、タイプセーフであるため、多くの場合より良い選択です。(C ++ 11以降)
  • すべての可変引数が共通の型を共有する場合、std :: initializer_listは、可変引数にアクセスするための便利なメカニズム(構文は異なります)を提供します。

以下は、両方の選択肢を示す例です(実際にご覧ください)。

#include <iostream>
#include <string>
#include <initializer_list>

template <typename T>
void func(T t) 
{
    std::cout << t << std::endl ;
}

template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
    std::cout << t <<std::endl ;

    func(args...) ;
}

template <class T>
void func2( std::initializer_list<T> list )
{
    for( auto elem : list )
    {
        std::cout << elem << std::endl ;
    }
}

int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    func(1,2.5,'a',str1);

    func2( {10, 20, 30, 40 }) ;
    func2( {str1, str2 } ) ;
} 

あなたが使用している場合、gccまたはclang私達は使用することができますPRETTY_FUNCTIONの マジック変数を何が起こっているかを理解するのに役立つことができる機能の型シグネチャを表示します。たとえば、次のように使用します。

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

例の可変個関数の結果は次のようになります(実際に表示されます)。

void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello

Visual StudioではFUNCSIGを使用できます。

C ++ 11より前の更新

プレC ++ 11のための代替のstd :: initializer_listは、あろうSTD ::ベクターまたは他のいずれかの標準的なコンテナ

#include <iostream>
#include <string>
#include <vector>

template <class T>
void func1( std::vector<T> vec )
{
    for( typename std::vector<T>::iterator iter = vec.begin();  iter != vec.end(); ++iter )
    {
        std::cout << *iter << std::endl ;
    }
}

int main()
{
    int arr1[] = {10, 20, 30, 40} ;
    std::string arr2[] = { "hello", "world" } ; 
    std::vector<int> v1( arr1, arr1+4 ) ;
    std::vector<std::string> v2( arr2, arr2+2 ) ;

    func1( v1 ) ;
    func1( v2 ) ;
}

そして、のための代替可変長テンプレートはなり可変長引数の関数を、彼らはされていないものの、安全型と一般的に起こりやすいエラーと使用が安全ではないことができますが、唯一の他の潜在的な代替を使用することですデフォルト引数をそれは限られた用途を有しているが、。以下の例は、リンクされた参照内のサンプルコードの修正バージョンです。

#include <iostream>
#include <string>
#include <cstdarg>

void simple_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);

    while (*fmt != '\0') {
        if (*fmt == 'd') {
            int i = va_arg(args, int);
            std::cout << i << '\n';
        } else if (*fmt == 's') {
            char * s = va_arg(args, char*);
            std::cout << s << '\n';
        }
        ++fmt;
    }

    va_end(args);
}


int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    simple_printf("dddd", 10, 20, 30, 40 );
    simple_printf("ss", str1.c_str(), str2.c_str() ); 

    return 0 ;
} 

使用する可変引数機能することもで詳述されて、あなたが渡すことができ、引数の制約が付属してドラフトC ++標準のセクションでの5.2.2 関数呼び出しの段落7

特定の引数にパラメーターがない場合、引数は、受信側の関数がva_arg(18.7)を呼び出すことによって引数の値を取得できるように渡されます。左辺値から右辺値(4.1)、配列からポインタ(4.2)、および関数からポインタ(4.3)の標準変換は、引数式に対して実行されます。これらの変換後、引数に算術、列挙、ポインター、メンバーへのポインター、またはクラス型がない場合、プログラムの形式は正しくありません。引数が非PODクラス型(9節)の場合、動作は未定義です。[...]


あなたのtypenamevs class使用は意図的ですか?もしそうなら、説明してください。
kevinarpe 2016

1
@kevinarpeは意図的ではありませんが、何も変更しません。
Shafik Yaghmour 2016

最初のリンクは、おそらくen.cppreference.com/w/cpp/language/variadic_argumentsへのリンクになるはずです。
Alexey Romanov

関数をinitializer_list再帰的にすることは可能ですか?
idclev 463035818

33

C ++ 17ソリューション:完全な型安全性+優れた呼び出し構文

C ++ 11の可変個のテンプレートとC ++ 17の折りたたみ式の導入以来、呼び出し側で、可変関数であるかのように呼び出すことができるテンプレート関数を定義できますが、次の利点があります。 :

  • 強く型安全であること。
  • 引数の数の実行時情報なしで、または「停止」引数を使用せずに動作します。

これは混合引数型の例です

template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

そして、すべての引数に対して型の一致を強制する別のもの:

#include <type_traits> // enable_if, conjuction

template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;

template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
    std::cout << head;
    (std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!");   // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
                                               // print_same_type(3, ": ", "Hello, ", "World!");
                                                                                              ^

詳しくは:

  1. 可変テンプレート、別名パラメーターパック パラメーターパック(C ++ 11以降)-cppreference.com
  2. 折りたたみ式 式を倍(以降C ++ 17) - cppreference.com
  3. 参照してください。完全なプログラムのデモンストレーション coliru上を。

13
ある日、私が読めないことを願っていますtemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
エラディアン

1
「このことは、場合にのみ有効になっているとして、それを読ん@Eladian HeadTail... 同じです」「どこ同じ手段」std::conjunction<std::is_same<Head, Tail>...>。この最後の定義を、「Headはすべてと同じ」として読んでくださいTail...
YSC 2018

24

c ++ 11では次のことができます:

void foo(const std::list<std::string> & myArguments) {
   //do whatever you want, with all the convenience of lists
}

foo({"arg1","arg2"});

リスト初期化子FTW!


19

C ++ 11には、可変引数テンプレートを実行する方法があり、可変引数関数を持つ非常にエレガントでタイプセーフな方法につながります。Bjarne自身はの素敵な例与え可変引数テンプレートを使用してのprintfC ++ 11FAQます。

個人的には、私はこれを非常にエレガントであると考えているので、コンパイラーがC ++ 11可変引数テンプレートをサポートするまで、C ++の可変引数関数を気にすることすらありません。


@donlan-C ++ 17を使用している場合は、折りたたみ式を使用して状況を大幅に簡素化できる場合があります(ここで独創的に考えると,、折りたたみ式で演算子を使用できます)。そうでなければ、私はそうは思いません。
1

15

Cスタイルの可変関数は、C ++でサポートされています。

ただし、ほとんどのC ++ライブラリは代替イディオムを使用します。たとえば、'c' printf関数は可変引数を取るのに対し、c++ coutオブジェクトは<<タイプセーフティとADTに対処するオーバーロードを使用します(おそらく実装の単純さを犠牲にして)。


また、これは印刷のような関数の場合にのみ機能するようです。実際には、各引数に対して単一の引数関数の反復があります。それ以外の場合は、リストを初期化し、リストを最後まで渡すだけstd::initializer_listsです。そして、これはすでに単純なタスクにかなりの複雑さをもたらしています。
クリストファー

13

varargsやオーバーロード以外に、引数をstd :: vectorまたは他のコンテナー(例えばstd :: map)に集約することを検討できます。このようなもの:

template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);

このようにして、タイプセーフを取得し、これらの可変引数の論理的な意味が明らかになります。

確かに、このアプローチにはパフォーマンスの問題がある可能性がありますが、代金を支払うことができないことが確実でない限り、心配する必要はありません。これは、c ++に対する一種の「Pythonic」アプローチです...


6
よりクリーンなのは、ベクトルを強制しないことです。代わりに、STLスタイルのコレクションを指定するテンプレート引数を使用してから、引数のbeginメソッドとendメソッドを使用してそれを繰り返します。これにより、std :: vector <T>、c ++ 11のstd :: array <T、N>、std :: initializer_list <T>を使用したり、独自のコレクションを作成したりできます。
JensÅkerblom2013年

3
@JensÅkerblom私は同意しますが、これは、過剰なエンジニアリングを避けるために、当面の問題について分析する必要がある種類の選択です。これはAPI署名の問題であるので、意図/ユーザビリティ/ maintanabilityなどの、最大の柔軟性と透明度との間のトレードオフを理解することが重要である
フランチェスコ

8

ここで説明するように、唯一の方法はCスタイルの変数引数を使用することです。これはタイプセーフではなく、エラーが発生しやすいため、推奨される方法ではありません。


エラーが発生しやすいということは、潜在的に非常に非常に危険なことを意味していると思いますか?特に、信頼できない入力を扱う場合。
ケビン・ロニー

はい、しかし型安全性の問題のためです。通常のprintfが持つ可能性のあるすべての問題を考えてみてください。渡された引数と一致しないフォーマット文字列などです。printfも同じ手法を使用します。
Dave Van den Eynde、2009年

7

Cスタイルの可変引数(...)に頼らずにこれを行う標準のC ++方法はありません。

もちろん、コンテキストに応じて可変数の引数のように「見える」デフォルトの引数があります。

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

4つの関数呼び出しはすべてmyfunc、引数の数を変えて呼び出します。何も指定されていない場合は、デフォルトの引数が使用されます。ただし、末尾の引数しか省略できないことに注意してください。たとえば、を省略iして与えるだけの方法はありませんj


4

オーバーロードまたはデフォルトのパラメーターが必要な場合があります-デフォルトのパラメーターで同じ関数を定義します。

void doStuff( int a, double termstator = 1.0, bool useFlag = true )
{
   // stuff
}

void doStuff( double std_termstator )
{
   // assume the user always wants '1' for the a param
   return doStuff( 1, std_termstator );
}

これにより、4つの異なる呼び出しのいずれかでメソッドを呼び出すことができます。

doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );

...または、Cのv_args呼び出し規約を探している可能性があります。


2

提供される引数の数の範囲がわかっている場合は、次のような関数オーバーロードをいつでも使用できます

f(int a)
    {int res=a; return res;}
f(int a, int b)
    {int res=a+b; return res;}

等々...


2

可変テンプレートを使用してconsole.log、JavaScriptで見られるように再現する例:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

ファイル名例js_console.h

#include <iostream>
#include <utility>

class Console {
protected:
    template <typename T>
    void log_argument(T t) {
        std::cout << t << " ";
    }
public:
    template <typename... Args>
    void log(Args&&... args) {
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void warn(Args&&... args) {
        cout << "WARNING: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void error(Args&&... args) {
        cout << "ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }
};


0

今では可能です... boost anyとテンプレートを使用しますこの場合、引数の型を混合できます

#include <boost/any.hpp>
#include <iostream>

#include <vector>
using boost::any_cast;

template <typename T, typename... Types> 
void Alert(T var1,Types... var2) 
{ 

    std::vector<boost::any> a(  {var1,var2...});

    for (int i = 0; i < a.size();i++)
    {

    if (a[i].type() == typeid(int))
    {
        std::cout << "int "  << boost::any_cast<int> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(double))
    {
        std::cout << "double "  << boost::any_cast<double> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(const char*))
    {
        std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
    }
    // etc
    }

} 


void main()
{
    Alert("something",0,0,0.3);
}

0

CとC ++のソリューションを組み合わせて、意味的に最も単純で、パフォーマンスが高く、最も動的なオプションを実現します。失敗した場合は、別の方法を試してください。

// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
  T * arr = new T[n];
  va_list ap;
  va_start(ap, n);
  for (size_t i = 0; i < n; i++)
    T[i] = va_arg(ap,T);
  return arr;
}

ユーザーの書き込み:

auto arr = spawn<float> (3, 0.1,0.2,0.3);

意味的には、これはn引数関数とまったく同じように見えます。フードの下では、いずれかの方法で開梱することがあります。


-1

すべての引数がconstで同じ型の場合、initializer_listを使用することもできます


-2
int fun(int n_args, ...) {
   int *p = &n_args; 
   int s = sizeof(int);
   p += s + s - 1;
   for(int i = 0; i < n_args; i++) {
     printf("A1 %d!\n", *p);
     p += 2;
   }
}

プレーンバージョン


1
また、x86以外では機能しない未定義の動作。
SSアン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.