How to pass multiple parameters - unknown number/type at compile time?

Started by
11 comments, last by Zahlman 18 years, 8 months ago
Not sure exactly how to word this, but what's the best method of passing parameters to a function in C++, when you don't know the number or type of parameters at compile time? Is this possible? The type includes class instances as well as integers, characters, floats etc. Thanks
---------------------http://www.stodge.net
Advertisement
Use va_lists as such: (taken from msdn examples)

/* The program below illustrates passing a variable * number of arguments using the following macros: *      va_start            va_arg              va_end *      va_list             va_dcl (UNIX only) */#include <stdio.h>#define ANSI            /* Comment out for UNIX version     */#ifdef ANSI             /* ANSI compatible version          */#include <stdarg.h>int average( int first, ... );#else                   /* UNIX compatible version          */#include <varargs.h>int average( va_list );#endifint main( void ){   /* Call with 3 integers (-1 is used as terminator). */   printf( "Average is: %d\n", average( 2, 3, 4, -1 ) );   /* Call with 4 integers. */   printf( "Average is: %d\n", average( 5, 7, 9, 11, -1 ) );   /* Call with just -1 terminator. */   printf( "Average is: %d\n", average( -1 ) );}/* Returns the average of a variable list of integers. */#ifdef ANSI             /* ANSI compatible version    */int average( int first, ... ){   int count = 0, sum = 0, i = first;   va_list marker;   va_start( marker, first );     /* Initialize variable arguments. */   while( i != -1 )   {      sum += i;      count++;      i = va_arg( marker, int);   }   va_end( marker );              /* Reset variable arguments.      */   return( sum ? (sum / count) : 0 );}#else       /* UNIX compatible version must use old-style definition.  */int average( va_alist )va_dcl{   int i, count, sum;   va_list marker;   va_start( marker );            /* Initialize variable arguments. */   for( sum = count = 0; (i = va_arg( marker, int)) != -1; count++ )      sum += i;   va_end( marker );              /* Reset variable arguments.      */   return( sum ? (sum / count) : 0 );}#endif
Domine non secundum peccata nostra facias nobis
My best bet would be the ... "parameter".
It's actually called a variable argument list.

But I don't really like it...
It's used in the printf kind of functions (among others).
Though I prefer the std::cout operator <<, but that's just me.


To come back to your original question.
Look at this site:
http://www.cprogramming.com/tutorial/lesson17.html

It explains a bit of the variable arguments list.
I think this is only good for POD types (int, char etc)? I'm also interested in passing objects around.

Thanks
---------------------http://www.stodge.net
Yes, variable argument lists only work for builtin types. Not to mention that:
Quote:Original quote from SiCrane, immortalised in Zahlman's sig:
"Basically whenever you invoke the dread ellipses construct you leave the happy world of type safety."

If you really want to do this google for discriminated unions (aka. variants) or look at boost::variant, boost::many and/or boost::any. However, it may be that there's a simpler/better way of doing what you want to do, such as templates or operator chaining (like how c++ streams work). What exactly is it you're wanting to do?

Enigma
Operator chaining for the win!!!

Example from my latest "drawing board" workspace (think of it as a sketchpad for sourcecode):

	graphics::scene_type scene;	scene.insert		( graphics::square   ( range(-100.0,+100.0) , range(-100.0,+100.0) ) )		( graphics::triangle ( range(-100.0,+100.0) , range(-100.0,+100.0) ) )		;


How'd I get this to compile? That's simple, insert() returns an object which has a function named operator(). For example, if I wanted the above to simply recall insert() for every extra "argument", I could do something similar to this:

namespace graphics {	class scene_type;	class scene_inserter {		scene_type & scene;	public:		scene_inserter( scene_type & scene )			: scene( scene )		{		}		template < typename type >		scene_inserter & operator()( type );	};		class scene_type : public renderable_type {		...	public:		template < typename type >		scene_inserter insert( type object );	};	template < typename type >	scene_inserter & scene_inserter::operator()( type object ) {		scene.insert( object );		return *this;	}	template < typename type >	scene_inserter scene_type::insert( type object ) {		//implement the adding of the object into an internal list or similar		return scene_inserter( *this );	}}
You may want to look like the source code for various scriptining languages. Some of them support calls to arbitrary C++ functions. In particular, I believe AngelCode, produced by one of our affiliates, supports calls that include C++ objects.
I don't understand the operator chaining!

Basically I have a ScriptManager that has a function named Call. It is invoked by C++. Call looks like this at the moment:

void Call(const char* name, const char* args, ...);

Call essentially invokes the Lua function name, and passes it the arguments from the va_list. I want it to be a generic Call function, hence my question.

I'm using ToLua++ for creating C++ bindings for Lua, but I'll see if Boost will solve my problem.
---------------------http://www.stodge.net
Vectors need to know the types at compile time I think.
---------------------http://www.stodge.net
Quote:Original post by stodge
I don't understand the operator chaining!


Okay, a more explanitory example then :-).

Basically, operator chaining is where you use operators to make it more obvious things are on the same level.

If we were to implement a function called "Add" without variable arguments, we'd have to call it multiple times to add numbers together:
Add( Add( Add( 3 , 4 ) , 5 ) , 6 )

Obviously, using operator+ makes this clearer:
3 + 4 + 5 + 6

Now with C++ you can overload operators yourself, as long as we get things started with a custom type - you can't overload (3 + 4) to evaluate to anything but 7.

iostreams use operator chaining, for example:
std::cout << 3 << 4;

This is clearer than: Print( Print( std::cout , 3 ) , 4 )

However, that's basically what's happening on the inside.

In this case, std::cout is of the type std::ostream, so there's some code somewhere that looks somewhat like this:
std::ostream & operator<<( std::ostream & os , int number ) {    //print number using the stream os    return os;}

Then, the compiler internally translates this:
std::cout << 3 << 4;

Into:
operator<<( operator<<( std::cout , 3 ) , 4 );

This topic is closed to new replies.

Advertisement