Appleshell
Appleshell

Reputation: 7388

How does this variable argument notation work?

In C++ I can define a function with a variable number of arguments like this:

void a(int a...) {
    std::cout << a << std::endl;
}

And call it like this:

a(100, 200, 300);

However, apparently I can only access the first argument: The output of the call is 100.

How do I access the other arguments with this notation?

Upvotes: 0

Views: 229

Answers (5)

Bill Door
Bill Door

Reputation: 18946

Sample code using your function a:

#include <iostream>
#include <cstdarg>

void a(int a...)
{
    va_list args;
    va_start(args, a);
    int b = va_arg(args, int);
    int c = va_arg(args, int);

    std::cout << a << ", " << b << ", " << c << std::endl;
}

int main()
{
    a(100, 200, 300);
    return 0;
}

The variable argument syntax does not know the number or type of the parameters. Therefore something in the parameter list must indicate the number and possibly the type of the parameters. There are several methods that are typically used to determine the number of parameters:

  1. The first parameter is the number of parameters.
  2. The last parameter is a delimiter, a NULL or other unique value, for example.
  3. The first parameter has other embedded information that determines the number, order and type of the parameters, for example the format parameter for printf.

In this example, I've simply assumed there are three parameters. Calling a with more or less parameters will cause undefined behavior (read, random results, to a crash).

Upvotes: 0

Jarod42
Jarod42

Reputation: 218098

A solution which enforces the type int.
But with a usage a little different

#include <initializer_list>
#include <iostream>

// Or you may use std::vector
void print(const std::initializer_list<int>& a) {
    for (auto elem : a) {
        std::cout << elem << std::endl;
    }
}

int main(int argc, char *argv[])
{
    print({1, 2, 3}); // extra braces.
    return 0;
}

Upvotes: 0

Mark
Mark

Reputation: 4109

Another variadic solution:

template<typename T, typename... Vs>
void print(T&& t, Vs&&... vs) {
  std::cout << std::forward<T>(t) << std::endl;
  int sink[] { (std::cout << " " << std::forward<Vs>(vs) << std::endl, 0)... };
  (void)sink; // silence "unused variable" warning                                        
}

Which has the benefit of not requiring helpers. We use pack expansion to forward each argument one at a time to cout. For syntax reasons, I leverage the comma operator so that the expression (cout..stuff.., 0) resolves to an integer, which we then discard into an array; this lets us use the pack expansion operator around our complex statement.

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275800

Your syntax is ... unfortunate, and refers to a C-style vararg function.

In C++11 you should prefer variardic templates. The easiest approach is something like this:

First, some helper code:

#include <utility>
template<typename Lambda>
void for_each_arg( Lambda&& unused ) {}

template<typename Lambda, typename Arg1, typename... Args>
void for_each_arg( Lambda&& closure, Arg1&& arg1, Args&&... args ) {
  closure( std::forward<Arg1>(arg1) );
  for_each_arg( std::forward<Lambda>(closure), std::forward<Args>(args)... );
}

now, we use it:

#include <iostream>
template<typename... Args>
void foo( Args&&... args ) {
  for_each_arg( [](int x){
    std::cout << x << "\n";
  }, std::forward<Args>(args)... );
}
int main() {
  foo( 1, 2, 3 );
}

and we can access each argument, and ensure that they convert to int. Note that the conversion to int is deferred until call of the body of for_each_arg.

Upvotes: 4

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153995

If you use a var args interface you need to be able to tell from the named parameters how many arguments where provided in total. For example, the <stdio.h> function do that by having the format string be the last named argument followed by as many arguments as specified in the argument list. To access the arguments you need to use the various va_... functions and types.

You are much better off using variadic templates:

template <typename... T>
void f(T... a) {
    // just expand the parameter pack over here
}

Upvotes: 1

Related Questions