Mathew Kurian
Mathew Kurian

Reputation: 6039

How do we change this function to support multiple arguments?

#include <iostream>    
using namespace std;

class SampleClass
{
  public:      
    int test(int ... arguments)
    {
        cout << arguments[0] << endl; // Access first element in array
        return sizeof(arguments);
    }
};

int main()
{   
   SampleClass lol;       
   cout << lol.test(3, 1, 4, 2, 5, 0) << endl;       
   return 0;
}

The test function fails due to my limited understanding in C++ semantics. But how can I fix it so that it can access the FIRST element in the arguments lits and then return the size of arguments?


As @Nik pointed out, I could obviously pass in an array, but there is no real fun with that! I am trying to do this just for learning - to see if this is even possible in C++.

Upvotes: 1

Views: 147

Answers (5)

Casey
Casey

Reputation: 42554

Since we're all guessing at what you want, I'll throw in:

template <typename ... Ts>
size_t test(Ts ... arguments) {
    auto unused = { 0, ((cout << '[' << arguments << "]\n"), 0)...};
    (void)unused;
    return sizeof...(arguments);
}

which works with different types of arguments (Live at Coliru). This solution is a bit too "clever" to be readable, though.

The trick here is to build a braced-initializer-list - the {...} stuff - whose elements are initialized by processing the variadic arguments in order. You then convince the compiler that said initializer list isn't used for anything, so the code will be optimized to just generate the desired side effects.

The comma operator in C++ evaluates to the value of the rightmost subexpression. The other subexpressions are evaluated and their values discarded. So ((cout << '[' << arguments << "]\n"), 0) has the effect of dumping some stuff to cout - including one of the variadic parameters - and evaluates to 0. After expanding the pack with the ... that line of code is effectively:

auto unused = { 0, ((cout << '[' << arg0 << "]\n"), 0),
                   ((cout << '[' << arg1 << "]\n"), 0),
                   ((cout << '[' << arg2 << "]\n"), 0) };

The cout junk is evaluated for its side effects and discarded, the whole thing is deduced as a std::initializer_list<int> just as if we had written

auto unused = { 0, 0, 0, 0 };

(The extra zero is there at the beginning to avoid a syntax error if someone calls the function with no arguments at all.)

The (void)unused; line is casting unused to void. It will compile to absolutely nothing, but also will typically tell compilers not to warn us about unused being an unused variable.

Upvotes: 3

WhozCraig
WhozCraig

Reputation: 66194

I am not quite sure I fully understand your question. If you want access to "the first" argument to the function rather than the template, I think something like this will do it for you, but I may be completely misunderstanding your purpose here:

#include <iostream>

template<typename... Args>
int foo(int arg0, Args... args);

template<>
int foo(int arg0)
{
    // here just to catch expansion
    std::cout << '[' << arg0 << ']' << std::endl;
    return 1;
}

template<typename... Args>
int foo(int arg0, Args... args)
{
    foo(arg0);
    foo(args...);
    return 1 + sizeof...(args);
}

int main()
{
    std::cout << foo(1,2,3,4,5) << std::endl;
    std::cout << foo(100,200,300) << std::endl;

    int a=10, b=20;
    std::cout << foo(a,b) << std::endl;

    return 0;
}

Output

[1]
[2]
[3]
[4]
[5]
5
[100]
[200]
[300]
3
[10]
[20]
2

Upvotes: 2

egur
egur

Reputation: 7960

You have several options.

1) use an ellipse (only way to have unlimited arg list):

int foo(int a1, ...);

Your code will need to parse the ellipse like printf does. you'll be limited to builtin C types.

2) Use multiple templates:

template<typename T1> int foo(T1 a1);
template<typename T1, typename T2> int foo(T1 a1, T2 a2);

// more templates for more arguments

This method us used, usually up to 10 parameters (10 template functions)

3) Use a function with defaults or illegal values so you'll know which is the last valid argument:

 int foo(int a1, int a2 = -1, int a3 = -1, int aN = -1);

Upvotes: 1

ForEveR
ForEveR

Reputation: 55887

Hm. You are trying to mix two features of C++, variadic-templates and variable-length argument list. Your code will not compile at all, since you have no templates here and for variable-length argument list declaration should be

int test(int arguments...)

and you can access values from this list with functions from cstdarg header.

With variadic-templates you can do following thing

class Derived
{
public:
    template<int... arguments>
    int test()
    {
       int array[] = {arguments...};
       return sizeof(array) / sizeof(*array);
    }
};

use it like

cout << lol.test<3, 1, 4, 2, 5, 0>() << endl;

Upvotes: 3

Bjorn
Bjorn

Reputation: 465

try something like this

double test( int num, ... )
{
  va_list arguments;                     // A place to store the list of arguments
  double sum = 0;

  va_start ( arguments, num );           // Initializing arguments to store all values after num
  for ( int x = 0; x < num; x++ )        // Loop until all numbers are added
    sum += va_arg ( arguments, double ); // Adds the next value in argument list to sum.
  va_end ( arguments );                  // Cleans up the list

  return sum / num;                      // Returns the average
}

so youre points are on the wrong side of your parameter list. i hope this helps and goodluck.

Upvotes: 3

Related Questions