web-dev-qa-db-ja.com

C + +の引数の可変数?

可変数の引数を受け入れる関数を書くにはどうすればいいですか?これは可能でしょうか。

234
nunos

あなたはおそらくそうすべきではありません、そしてあなたはおそらくより安全で簡単な方法でやりたいことをすることができます。技術的に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<<()のような)ビルダーパターン、またはデフォルト引数などを使用することを検討してください。これらはすべて安全です。あなたがあなたの足を吹き飛ばす前にあなたを止めなさい。

133
wilhelmtell

C++ 11では、Alternatives section可変数関数 リファレンスページのように、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_FUNCTIONmagic variable を使用できます。何が起こっているのかを理解するのに役立つことがある関数の型シグネチャを表示します。例えば:

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

この例では、可変個の関数について次のようなint型の結果が得られます(liveを参照)。

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以前のアップデート

Pre C++ 11std :: initializer_list の代わりのものは std :: vector または他の一つになるでしょう 標準コンテナ

#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 ) ;
}

そしてvariadic templatesの代わりになるのは variadic関数 ではないでしょう--- type-safeそして一般に エラー を使用するのは危険であり危険ですが、他の唯一の可能性のある代替方法はデフォルト引数を使用することです。以下の例は、リンクされた参照のサンプルコードの修正版です。

#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 ;
} 

variadic functionsを使用すると、渡すことができる引数に制限があります。これについては、 ドラフトC++標準 のセクション5.2.2Function callに詳しく説明されています。段落7

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

336
Shafik Yaghmour

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
Markus Zancolò

C++ 11には、可変引数テンプレートを作成する方法があります。これは、可変引数関数を持つための本当に優雅でタイプセーフな方法を導きます。 Bjarne自身が C++ 11FAQ の中で printfを可変引数テンプレートを使って のいい例を挙げています。

個人的には、私はこれをとても優雅だと考えています。そのコンパイラーがC++ 11可変引数テンプレートをサポートするまで、私はC++で可変引数関数を使用することすらしません。

17
Omnifarious

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

しかし、ほとんどのC++ライブラリは別の慣用句を使用します。 'c' printf関数は可変引数を取りますが、c++ coutオブジェクトは型安全性とADTに対処する<<オーバーロードを使用します(おそらく実装の単純さを犠牲にして)。

15
Will

引数やオーバーロードとは別に、あなたは自分の引数を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);

このようにすれば、型安全性が得られ、これらの可変引数の論理的意味が明らかになります。

確かにこのアプローチにはパフォーマンスの問題がある可能性がありますが、代金を払えないことが確実でない限り、それらについて心配するべきではありません。これは一種の "Pythonic"アプローチであり、c ++ ...

13
Francesco

C++ 17の解決策:フルタイプセーフティ+素晴らしい呼び出し構文

C++ 11で可変数テンプレートが導入され、C++ 17でフォールド式が導入されたため、呼び出し先サイトでは、あたかもそれが可変長関数であるかのように呼び出すことができる:

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

これは引数が混在する型の例です。

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の 完全なプログラムのデモ を参照してください。
11
YSC

唯一の方法は、 ここ で説明されているように、Cスタイルの変数引数を使用することです。これは型保証がなく、エラーが発生しやすいため、これはお勧めできません。

8

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だけを指定する方法はありません。

7
Zoli

多重定義やデフォルトのパラメータが欲しいかもしれません - デフォルトのパラメータで同じ関数を定義します:

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つの異なる呼び出しのうちの1つでメソッドを呼び出すことができます。

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

...あるいは、Cのv_args呼び出し規約を探しているかもしれません。

4
Kieveli

他の人が言っているように、Cスタイルの引数。しかし、デフォルトの引数でも同様のことができます。

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

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

等々...

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;
   }
}

普通版

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

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;
    }
};
1
lama12345

今は可能です... 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
Aftershock

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

0
pkumar0