03 Mar, 2012, Kline wrote in the 1st comment:
Votes: 0
What I'd like to accomplish is implementing a va_list similar function for logging/message outputs to avoid redundantly snprintf() into a temporary buffer and sending it various places. What I don't want, though, are "random" segfaults caused by the wrong #/type of args sent to vsprintf. Even using GCC's __attribute__ specifier for printf checking the compiler (g++ 4.6.2) doesn't kick any warnings or errors back when I purposely build an invalid vsprintf line.

I spent most of yesterday trying to find something small (not a library) I could work with to add into my own code that is either a type-safe implementation of vsprintf or a "sprintf formater specifier" similar way of working with C++ strings and/or stringstreams. I know strings have format specifiers, but man, that's verbose and clunky. I'd like to ideally retain the format specifiers of sprintf() yet have some kind of type-safety in a custom implementation, which I can't seem to figure out how to do.

Has anyone else attempted/done something similar and could give me some advice on this?

edit: Boost.Format looks like it would be perfect, but, I'd really like to not have external dependencies for anything.
03 Mar, 2012, Davion wrote in the 2nd comment:
Votes: 0
You can write your own string library. I accomplished this by overloading operators to account for the different types.

Here's a snippet

String &String::operator<<(int i)
{ *this += i;
return *this;
}
String &String::operator<<(double i)
{ *this += i;
return *this;
}
String &String::operator<<(const char *add)
{ *this += add;
return *this;
}

String &String::operator<<(const String &add)
{ *this += add;
return *this;
}
String &String::operator<<(char add)
{ *this += add;
return *this;
}


The += is just an overloaded operator to concat. You'll have to of course define which types are safe to use, but with this you'll be able to use which ever ones you write. This doesn't give you the printf style formatting, but I'm sure it wouldn't be to hard to add.
04 Mar, 2012, Omega wrote in the 3rd comment:
Votes: 0
This is what I use, I wrote this from memory as I am at work so not 100% if it will work without some minor tweaking.

The only thing it cannot do is C++ types. (yet) ie..

c->writeBuffer(Format("%s is awesome", c->getName())); // error POD type
c->writeBuffer(Format("%s is awesome", c->getName().c_str())); // non-error



Here it is!
std::string Format(const std::string &fmt, …)
{
// first we get the size (vsnprintf returns the length of the string
// so we get that first)
va_list args;
va_start(args, fmt.c_str());
int len = vsnprintf(NULL, 0, fmt.c_str(), args);
va_end(args);

// now we create a nice buffer of space to push the string into
std::vector<char>buf;
buf.resize(len+1);

// actually do the formatting into the newly created buffer
va_start(args, fmt.c_str());
vsnprintf(&buf[0], (len+1), fmt.c_str(), args);
va_end(args);

// create our return-string
std::string retStr(&buf[0]);
return retStr;
}
04 Mar, 2012, Kline wrote in the 4th comment:
Votes: 0
Sweet, thanks guys! I'll play around with both ideas some today.
05 Mar, 2012, Omega wrote in the 5th comment:
Votes: 0
std::string Format(const char *fmt, …)
{
// first we get the size
va_list args;

va_start(args, fmt);
int len = vsnprintf(NULL, 0, fmt, args);
va_end(args);

// vsnprintf will return the amount of space required for the string
// so now we create a nice buffer of space to push it into
std::vector<char>buf;
buf.resize(len+1);

// actually do the formatting into the buffer
va_start(args, fmt);
vsnprintf(&buf[0], (len+1), fmt, args);
va_end(args);

// create our return-string
std::string retStr(&buf[0]);
return retStr;
}


This is the correct code (straight from my mud this time, not from memory). This works nicely :)
05 Mar, 2012, arholly wrote in the 6th comment:
Votes: 0
Yes, yes it does.
05 Mar, 2012, Kline wrote in the 7th comment:
Votes: 0
Ok, so, here's what I've ended up at. It's still not 100% type-safe, but it does have some additional safety checks in place and points exactly to where the problem is. sint_t and uint_t are typedef'ed to signed/unsigned ints based on numeric_limits.

namespace Utils {
const string _FormatString( const sint_t narg, const bitset<MAX_BITSET> flags, const string caller, const string fmt, … );
const string __FormatString( const sint_t narg, const bitset<MAX_BITSET> flags, const string caller, const string fmt, va_list val );
#define FormatString( flags, fmt, … ) _FormatString( PP_NARG( __VA_ARGS__ ), flags, _caller, fmt, ##__VA_ARGS__ )
const void _Logger( const sint_t narg, const bitset<MAX_BITSET> flags, const string caller, const string fmt, … );
#define Logger( flags, fmt, … ) _Logger( PP_NARG( __VA_ARGS__ ), flags, _caller, fmt, ##__VA_ARGS__ )
const vector<string> StrTokens( const string input );
template <class T> inline const string toString( const T& t ) { stringstream ss; ss << t; return ss.str(); }
#define toString_( T ) toString( T ).c_str()
};

#define STR(x) #x
#define SX(x) STR(x)
#define _caller __FILE__ ":" SX(__LINE__)

// Thanks to Laurent Deniau @ https://groups.google.com/d/msg/comp.std...
#define PP_NARG(…) \
PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(…) \
PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,…) N
#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0

const string Utils::_FormatString( const sint_t narg, const bitset<MAX_BITSET> flags, const string caller, const string fmt, … )
{
va_list args;
string output;

va_start( args, fmt );
output = __FormatString( narg, flags, caller, fmt, args );
va_end( args );

return output;
}

const string Utils::__FormatString( const sint_t narg, const bitset<MAX_BITSET> flags, const string caller, const string fmt, va_list val )
{ // Thanks go to Darien @ MudBytes.net for the start of this
va_list args;
vector<string> arguments;
vector<string>::iterator si;
vector<char> buf;
string output, token;
sint_t size = 0;

arguments = StrTokens( fmt );
for ( si = arguments.begin(); si != arguments.end(); si++ )
{
token = *si;
if ( token.find( "%" ) != string::npos ) // not foolproof, but it should catch some worst cases by attempting
size++; // to ensure a matching narg : format specifier count
}

if ( narg != 1 && narg != size ) // if narg == 1 invocation was func( flags, string )
{
Logger( flags, "ERROR: Number of arguments (%ld) did not match number of format specifiers (%ld) at: %s", narg, size, caller.c_str() );
return output = "";

}

va_copy( args, val );
size = vsnprintf( NULL, 0, fmt.c_str(), args );
va_end( args );

va_copy( args, val );
buf.resize( size + 1 );
vsnprintf( &buf[0], ( size + 1 ), fmt.c_str(), args );
va_end( args );

return output = &buf[0];
}

const void Utils::_Logger( const sint_t narg, const bitset<MAX_BITSET> flags, const string caller, const string fmt, … )
{
va_list args;
string output;

va_start( args, fmt );
output = __FormatString( narg, flags, caller, fmt, args );
va_end( args );


if ( output.empty() )
return;

if ( flags.test(UTILS_DEBUG) ) // output caller
clog << current_time_str() << " :: " << output << " [" << caller << "]" << endl;
else
clog << current_time_str() << " :: " << output << endl;

return;
}

const vector<string> Utils::StrTokens( const string input )
{
stringstream ss( input );
istream_iterator<string> si( ss );
istream_iterator<string> end;
vector<string> output( si, end );

return output;
}


Incorrect invocation:
Utils::Logger( 0, "test", 5, 10 );

Error'ed output:
Mon Mar  5 13:26:49 2012 :: ERROR: Number of arguments (2) did not match number of format specifiers (0) at: act_info.c:5185


So I haven't (yet) managed to automagically save attempts at stuffing an int into a string specifier and stuff, but, I'm working toward it :).
Specifying every format specifier as a string and then converting all arguments to strings does work safely, and seems like it will be how I end up sanitizing user inputs.

Invocation:
Utils::Logger( 0, "test %s %s", Utils::toString_( 5 ), Utils::toString_( 10 ) );

Output:
Mon Mar  5 13:37:36 2012 :: test 5 10
0.0/7