PDA

View Full Version : va_start used in function with fixed args



stinos
26th November 2008, 15:58
Hi,

I'm not too keen on variable argument lists, but I'd like to use them for some logging functions.
Suppose the prototype for logging functions is

void Log( const String& sMessage, const int iLevel, const bool bUseLineEnd );

String already takes variable argument lists in it's sPrintf function, which also returns a reference to this, so I could use

Log( String().sPrintf( "test %d", 5 ), level, true ); for example.

That's not really a problem, but what happens: String is constructed, then allocates memory, then calls sprintf and returns a reference to itself. Internally, Log copies the memory from String into pre-allocated space. The call returns, and String and it's memory are deallocated.
What I would rather like to see, is that the sprintf call happens directly on the pre-allocated memory in Log. That saves allocating, memcpy and deallocation. However, I would also like to keep the same prototype, so that I can use Log( "test %d", 5, level, true );

Variable arguments can't be listed first, so my first thought was to simply declare all possible functions:

template< class T0 >
void Log( const char* Format, const T0 arg0, const int iLevel, const bool bLineEnd );
template< class T0, class T1 >
void Log( const char* Format, const T0 arg0, const T1 arg1, const int iLevel, const bool bLineEnd );

and so on.
Implementating this was easy at first sight: let the Logger class have a method that accepts a va_list and construct the list from the arguments.
template< class T0 >
void Log( const char* Format, const T0 arg0, const int iLevel, const bool bLineEnd )
{
va_list args;
va_start( args, Format );
InternalLogFunction( Format, args, iLevel, bLineEnd );
va_end( args );
}

This does excatly what I want, though it's not overly pretty, but gcc (4.1.2) chokes on this saying "va_start used in function with fixed args".
I don't really understand why it is not allowed (afaik va_start just lets args point to the next element on the stack, arg0 in this case), can someone explain this?
Also, what would be other ways to achieve this? What I basically want is a variable argument list of some kind, followed by 2 named parameters. (ok I could just use Log( const int, const bool, const char*, ... ) but it would just make writing much easier if it behaved like the non-printf Log)

Thanks!

jacek
26th November 2008, 19:10
Why do you need va_start in that function?

stinos
26th November 2008, 20:37
Why do you need va_start in that function?

I'm using void InternalLogFunction( char* Format, va_list& Args, const int, const bool ); so i need va_start to initialize the va_list.

jacek
26th November 2008, 22:25
I'm using void InternalLogFunction( char* Format, va_list& Args, const int, const bool ); so i need va_start to initialize the va_list.
Indeed, I missed that.

The problem is that the standard doesn't say anything about using va_list in a function with fixed number of parameters, so compilers might behave differently. The parameter you pass to va_start() is defined as the rightmost one and if you follow the standard strictly this makes va_start() useless in your case.

The easiest solution would be to move those last two parameters to the front, but I assume you can't do that, so instead you could change your InternalLogFunction() to something similar to qDebug() stream:

void Log( const char* Format, const T0 arg0, const int iLevel, const bool bLineEnd )
{
InternalLogStream( iLevel, bLineEnd, Format ) << arg0;
}
or something like that.

stinos
27th November 2008, 08:08
The easiest solution would be to move those last two parameters to the front, but I assume you can't do that, so instead you could change your InternalLogFunction() to something similar to qDebug() stream:


didn't think about that. Wouldn't it require partly reinventing the printf mechanism?

Suppose a case with 3 parameters:
Log( "%d %s %d", 3, "test", 3, level, true );
that would use
InternalLogStream( iLevel, bLineEnd, Format ) << arg0 << arg1 << arg2;
Each call to << should move on to the next '%' found in the format string, convert the argument, append result to internal string. That's what printf does..


edit after thinking it through: it's really not a bad idea at all. It would require a call to 'Flush' or so after the InternalLogStream() calls because it has no way of knowing which argument is the last one, but I like the fact it uses templates: there's no way you can write a call with an argument that doesn't support conversion as the compiler would spot it immedeately so it's safer than normal printf. It might be somewhat slower though, still have to figure that out.

jacek
28th November 2008, 00:49
Wouldn't it require partly reinventing the printf mechanism?
Yes, but you can't use printf() because of those last two parameters.