mike
mike

Reputation: 51

C++ how to use ellipsis without a preceding argument

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.

Upvotes: 2

Views: 10703

Answers (5)

Ikac03
Ikac03

Reputation: 73

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

Upvotes: 0

rcollyer
rcollyer

Reputation: 10695

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;

Upvotes: 2

Mark B
Mark B

Reputation: 96291

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.

Upvotes: 1

Eric
Eric

Reputation: 19873

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

Upvotes: 3

Tony Delroy
Tony Delroy

Reputation: 106226

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

Upvotes: 0

Related Questions