ChatGPT解决这个技术问题 Extra ChatGPT

Variable number of arguments in C++?

How can I write a function that accepts a variable number of arguments? Is this possible, how?

At this time with C++11 the answers for this question would greatly differ
@K-ballo I added C++11 examples as well since a recent question asked this same thing recently and I felt this needed one in order to justify closing it stackoverflow.com/questions/16337459/…
Added pre C++11 options to my answer as well, so it now should cover most of the choices available.
@K-ballo there is afaik no way to do it in C++ in case you need forced argument type.. no construction like foo(int ... values) :/ If you don't care about types, then yes, variadic templates in C++11 works great

c
cincodenada

In C++11 you have two new options, as the Variadic arguments reference page in the Alternatives section states:

Variadic templates can also be used to create functions that take variable number of arguments. They are often the better choice because they do not impose restrictions on the types of the arguments, do not perform integral and floating-point promotions, and are type safe. (since C++11) If all variable arguments share a common type, a std::initializer_list provides a convenient mechanism (albeit with a different syntax) for accessing variable arguments.

Below is an example showing both alternatives (see it live):

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

If you are using gcc or clang we can use the PRETTY_FUNCTION magic variable to display the type signature of the function which can be helpful in understanding what is going on. For example using:

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

would results int following for variadic functions in the example (see it 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

In Visual Studio you can use FUNCSIG.

Update Pre C++11

Pre C++11 the alternative for std::initializer_list would be std::vector or one of the other standard containers:

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

and the alternative for variadic templates would be variadic functions although they are not type-safe and in general error prone and can be unsafe to use but the only other potential alternative would be to use default arguments, although that has limited use. The example below is a modified version of the sample code in the linked reference:

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

Using variadic functions also comes with restrictions in the arguments you can pass which is detailed in the draft C++ standard in section 5.2.2 Function call paragraph 7:

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.7). The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. [...]


Is your typename vs class usage above intentional? If so, please explain.
@kevinarpe not intentional, it does not change anything though.
Your first link should probably be to probably be to en.cppreference.com/w/cpp/language/variadic_arguments instead.
is it possible to make a function taking a initializer_list recursive?
w
wilhelmtell

You probably shouldn't, and you can probably do what you want to do in a safer and simpler way. Technically to use variable number of arguments in C you include stdarg.h. From that you'll get the va_list type as well as three functions that operate on it called va_start(), va_arg() and 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;
}

If you ask me, this is a mess. It looks bad, it's unsafe, and it's full of technical details that have nothing to do with what you're conceptually trying to achieve. Instead, consider using overloading or inheritance/polymorphism, builder pattern (as in operator<<() in streams) or default arguments etc. These are all safer: the compiler gets to know more about what you're trying to do so there are more occasions it can stop you before you blow your leg off.


Presumably, you cannot pass references to a varargs function because the compiler wouldn't know when to pass by value and when by reference, and because the underlying C macros would not necessarily know what to do with references -- there are already restrictions on what you can pass into a C function with variable arguments because of things like promotion rules.
is it necessary to provide atleast one argument before the ... syntax?
@Lazer it is not a language or library requirement, but the standard library doesn't give you means to tell the length of the list. You need the caller to give you this information or else somehow figure it out yourself. In the case of printf(), for example, the function parses the string argument for special tokens to figure out how many extra arguments it should expect in the variable argument list.
you should probably use <cstdarg> in C++ instead of <stdarg.h>
Variable number of arguments is great for debug or for functions/methods that fill some array. Also it's great for many mathematical operations, such as max, min, sum, average... It's not mess when you don't mess with it.
Y
YSC

A C++17 solution: full type safety + nice calling syntax

Since the introduction of variadic templates in C++11 and fold expressions in C++17, it is possible to define a template-function which, at the caller site, is callable as if it was a varidic function but with the advantages to:

be strongly type safe;

work without the run-time information of the number of arguments, or without the usage of a "stop" argument.

Here is an example for mixed argument types

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

And another with enforced type match for all arguments:

#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!");
                                                                                              ^

More information:

Variadic templates, also known as parameter pack Parameter pack(since C++11) - cppreference.com. Fold expressions fold expression(since C++17) - cppreference.com. See a full program demonstration on coliru.


one day ill hope i can read template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
@Eladian read it as "This thing is enabled only if Head and Tail... are the same", where "are the same" means std::conjunction<std::is_same<Head, Tail>...>. Read this last definitinon as "Head is the same as all of Tail...".
Can we loop over args?
@AbbasEbadian Yes, but that's a question on its own.
M
Markus Zancolò

in c++11 you can do:

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

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

list initializer FTW!


Arguments can be general types (Int,Char,Float etc) this doesn't work for them.
S
Shafik Yaghmour

In C++11 there is a way to do variable argument templates which lead to a really elegant and type safe way to have variable argument functions. Bjarne himself gives a nice example of printf using variable argument templates in the C++11FAQ.

Personally, I consider this so elegant that I wouldn't even bother with a variable argument function in C++ until that compiler has support for C++11 variable argument templates.


@donlan - If you're using C++17, you can use fold expressions to make things a lot simpler in some cases (think creatively here, you can use the , operator with fold expressions). Otherwise, I don't think so.
Nice article referenced.. When I started reading the class template tuple in the section "We can build a variadic type" I suddenly thought: "OMG! This is Lisp programming, at compile time, in C++.." ..LOL..
W
Will

C-style variadic functions are supported in C++.

However, most C++ libraries use an alternative idiom e.g. whereas the 'c' printf function takes variable arguments the c++ cout object uses << overloading which addresses type safety and ADTs (perhaps at the cost of implementation simplicity).


Also, this only seems to work in the case of a function like printing, where you actually have an iteration of a single argument function across each arg. Otherwise, you are just initializing a list and passing the list for the end around on std::initializer_lists... And this is already introducing massive complexity on a simple task.
F
Francesco

Apart from varargs or overloading, you could consider to aggregate your arguments in a std::vector or other containers (std::map for example). Something like this:

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

In this way you would gain type safety and the logical meaning of these variadic arguments would be apparent.

Surely this approach can have performance issues but you should not worry about them unless you are sure that you cannot pay the price. It is a sort of a a "Pythonic" approach to c++ ...


Cleaner would be to not enforce vectors. Instead use a template argument specifying the STL-styled collection then iterate through it using the argument's begin and end methods. This way you can use std::vector, c++11's std::array, std::initializer_list or even make your own collection.
@JensÅkerblom I agree but this is the kind of choice that should be analyzed for the issue at hand, to avoid over engineering. Since this is a matter of API signature, it is important to understand the tradeoff between maximum flexibility and clarity of intent/usability/maintanability etc.
D
Dave Van den Eynde

The only way is through the use of C style variable arguments, as described here. Note that this is not a recommended practice, as it's not typesafe and error-prone.


By error prone I assume you mean potentially very very dangerous? Especially when working with untrusted input.
Yes, but because of the type safety issues. Think all of the possible issues that regular printf has: format strings not matching passed arguments, and such. printf uses the same technique, BTW.
Z
Zoli

There is no standard C++ way to do this without resorting to C-style varargs (...).

There are of course default arguments that sort of "look" like variable number of arguments depending on the context:

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

// other code...

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

All four function calls call myfunc with varying number of arguments. If none are given, the default arguments are used. Note however, that you can only omit trailing arguments. There is no way, for example to omit i and give only j.


k
kungfooman

Using variadic templates, example to reproduce console.log as seen in JavaScript:

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

Filename e.g. 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;
    }
};

K
Kieveli

It's possible you want overloading or default parameters - define the same function with defaulted parameters:

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

This will allow you to call the method with one of four different calls:

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

... or you could be looking for the v_args calling conventions from C.


O
Orif

C++ 11 with color code support

Is generic and works for all dataypes

Works like JavaScript console.log(1,"23")

Supports color codes for info, warning, error.

Example:

#pragma once
#include <iostream>
#include <string>

const std::string RED = "\e[0;91m";
const std::string BLUE = "\e[0;96m";
const std::string YELLOW = "\e[0;93m";

class Logger {
private:
  enum class Severity { INFO, WARN, ERROR };

  static void print_colored(const char *log, Severity severity) {
    const char *color_code = nullptr;

    switch (severity) {
    case Severity::INFO:
      color_code = BLUE.c_str();
      break;
    case Severity::WARN:
      color_code = YELLOW.c_str();
      break;
    case Severity::ERROR:
      color_code = RED.c_str();
      break;
    }

    std::cout << "\033" << color_code << log << "\033[0m -- ";
  }

  template <class Args> static void print_args(Args args) {
    std::cout << args << " ";
  }

public:
  template <class... Args> static void info(Args &&...args) {
    print_colored("[INFO] ", Severity::INFO);
    int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
    std::cout << std::endl;
  }

  template <class... Args> static void warn(Args &&...args) {
    print_colored("[WARN] ", Severity::WARN);
    int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
    std::cout << std::endl;
  }

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


D
Dunya Degirmenci

If you know the range of number of arguments that will be provided, you can always use some function overloading, like

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

and so on...


T
Thomas Padron-McCarthy

As others have said, C-style varargs. But you can also do something similar with default arguments.


Could you elaborate?
@QPaysTaxes: For an example of how to do it with default arguments, look at Zoltan's answer.
C
Chris
// 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;
}

User writes:

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

Semantically, this looks and feels exactly like an n-argument function. Under the hood, you might unpack it one way or the other.


A
Aftershock

It is possible now...using boost any and templates In this case, arguments type can be mixed

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

Why add an extra dependency? C++17 gives us a nice class std::any.
p
pkumar0

We could also use an initializer_list if all arguments are const and of the same type


В
Вениамин Раскин
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;
   }
}

Plain version


And undefined behavior that won't work on anything other than x86.
Writing assembly is even better than this.