Reputation: 51
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
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
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
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
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
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