2

Hi I have a class with a member function that takes a variable number of arguments. The class knows how many arguments to expect once it is instantiated. e.g

class myClass
{
 myClass(int num_args){na=num_args;};
 private:
 int na;
 public:
 void do_something(int num_args, ...);
}

void myClass::do_something(int num_args, ...)
{
 va_list vl;
 va_start(vl, num_args);
 for(int i=0; i < num_args; i++)
  do_anotherthing(va_arg(vl, type));
 va_end
}

so i end up calling as follows:

myClass A(5);
myClass B(4);

A.do_something(5, a, b, c, d, e);
B.do_something(4, a, b, c, d);

It seems untidy to me to have to keep specifying the number of arguments i'm passing. What's the best way to get around this? I've considered overloading a bunch of functions, each with n arguments, a macro to put the num_args in for me. But ideally I'd like to be able to define do_something as

void do_something(...);

and somehow get the stdargs stuff to work with the class's num_args rather than a value passed in.

Many thanks for your thoughts.

mike
  • 51
  • 1
  • 2
  • 8
  • 1
    It would appear C++0X's variadic templates would fit well here, but I don't know much about them or what compilers support them at this point. (g++ likely does) – Joe Mar 31 '11 at 03:48
  • 2
    @Joe: You rang, sir? http://stackoverflow.com/questions/5475046/any-metaprogramming-way-to-generate-overloads-for-various-numbers-of-template-par/5475163#5475163 – phooji Mar 31 '11 at 04:10

5 Answers5

3

Please don't do that. Variable number of parameters is very uncommon in C++

The best way to achieve what you want is to create a list of int and pass that list as a parameter

you can check the size of the list inside the function

Eric
  • 19,525
  • 19
  • 84
  • 147
  • 2
    I think most of the reason it's uncommon is just that it's hard to do and still be type-safe. Expect it to become more common as C++0x becomes more popular and more widely supported. – Rob Kennedy Mar 31 '11 at 04:18
  • 1
    as long as printf() (and its 50 cousins) are around, variable number won't be that uncommon. As far as implementation, we need to keep in mind the application. It is much cheaper computationally to simply push N number of arguments onto the stack and sacrifice some type safety rather than create lists of polymorphic objects (he never said all his params are ints). – DXM Mar 31 '11 at 08:19
2

I don't see why you can't use na in your function itself, i.e.

void myClass::do_something(int num_args, ...)
{
 va_list vl;
 va_start(vl, na);
 for(int i=0; i < na; i++)
   do_anotherthing(va_arg(vl, type));
 va_end
}

But, what is type? What type is do_anotherthing expecting?

Unfortunately, variadic methods are not type safe, and it is not clear whether or not va_arg will generate an error if vl is not the type you specify. Consider instead doing something like boost::format, i.e.

A % a % b % c % d % e;

where the operator% performs do_anotherthing on each element supplied until the max number is reached.

Edit: Upon further thought, it is probably best if do_something returned a helper object that does everything for it, such as

do_something_helper myClass::do_something() { 
  return do_something_helper( na ); }

struct do_something_helper {
  int count;
  do_something_helper( int c ) : count( c ) {}

  template< class T >
  do_something_helper& operator%( T val ) {
     --count;
     if ( count < 0 ) {
      //trigger some error condition
     }
     do_anotherthing( val );
     return *this;
  }

  ~do_something_helper() {
    if ( count > 0 ) { // too few args
      //trigger some error condition
    }
  }
}

which would have the usage

A.do_something() % a % b % c % d % e;
rcollyer
  • 10,475
  • 4
  • 48
  • 75
1

Instead of using varargs can you use operator<< like C++ standard streams? Then it's all type-safe and you don't have to worry about the issues you noted with varargs.

Mark B
  • 95,107
  • 10
  • 109
  • 188
0

I know this is not a good practice and there are better solutions to solve this issue, but just as a practice for ellipsis parameters I found it to be a nice exercise.

This code is working for my compiler (VS2013) and I am sorry that I had to use inline assembly but first idea to use local stack variable could not work because of stack organization. So here it is:

#include <iostream>
#include <cstdarg>
#include <stdint.h>

using namespace std;

double avg(double * sp, ...);

#define AVERAGE(n, ...)                         \
{                                               \
    int * stackP;                               \
    __asm {                                     \
        __asm mov ebx, esp                      \
        __asm mov stackP, ebx                   \
    }                                           \
    double a = avg(stackP, n, __VA_ARGS__);     \
    cout << "Average is: " << a << endl;        \
}

template <typename T>
double avg(int * sp, T n, ...)
{
    va_list list;
    va_start(list, n);
    double result = n;
    unsigned int count = 1;
    T * pArg = 0;

    while (sp > reinterpret_cast<int *>(pArg + 1))
    {
        pArg = &(va_arg(list, T));
        result += *pArg;
        count++;
    }

    va_end(list);

    result /= count;
    return result;
}

void main()
{
    AVERAGE(1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6);
    AVERAGE(1.2, 1.3, 1.4, 1.5, 1.6);
    AVERAGE(1.2, 1.3, 1.4);
    AVERAGE(2, 3, 4);
    AVERAGE(2, 3, 4, 5, 6);
}
Ikac03
  • 73
  • 8
0

What's the best way to get around this?

You could consider using a sentinel value so you don't have to count the elements - it's less (but still) error prone. Overloading may be even better from a usage-safety perspective, but clearly it becomes impractical if the number of arguments is often large and unpredictable.

Better yet, pass in a vector or array. With arrays, you can have the called function know the size ala:

template <size_t N>
void f(type (&x)[N])
{
    ...
}

Your example suggests the type is constant and known at compile time, so a vector or array should be adequate. If this is a simplification, consider a vector or array of boost variant or any, or perhaps a tuple, or perhaps a function that returns a reference to the object so you can chain calls providing successive values (many operators do this).

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252