Short version: How can I pass the contents represented by ... in a variable argument function to another function without first parsing it into a va_list?
Long version:
Below are two functions in a class of mine. I would like to draw your attention to the fact that the first four lines of each function are identical. And I have a half dozen other functions in this class with the same first four lines.
void cyclOps::Logger::warn(char* szFile, char* szFunction, int iLine, char* szFormat, ...) {
va_list vaArguments;
va_start(vaArguments, szFormat);
char szOutput[10000];
_vsnprintf_s(szOutput, CYCLOPSSIZEOF(szOutput), _TRUNCATE, szFormat, vaArguments);
this->log("WARNING: %s [%s - %s(%d)]", szOutput, szFile, szFunction, iLine);
}
void cyclOps::Logger::info(char* szFormat, ...) {
va_list vaArguments;
va_start(vaArguments, szFormat);
char szOutput[10000];
_vsnprintf_s(szOutput, CYCLOPSSIZEOF(szOutput), _TRUNCATE, szFormat, vaArguments);
this->log("INFO: %s", szOutput);
}
I would like to put these four identical lines in a single function called summarizeVariableArguments() and call it something like this...
void cyclOps::Logger::info(char* szFormat, ...) {
std::string strOutput = this->summarizeVariableArguments(/* TBD */);
this->log("INFO: %s", strOutput.c_str());
}
...where the contents of strOutput would be the same as the contents of szOutput in the two previous functions. But how do I pass the ... parameter to another function?
You cannot do that portably (or perhaps at compile time, with horrible C++2011 variadic template tricks).
If you want to call at runtime a variadic function, you may want to use the libffi.
Details are operating system, compiler, processor and ABI specific. (but libffi is trying to abstract them).
That's what perfect forwarding is all about + variadic templates.
template<typename ...Args>
void cyclOps::Logger::info(char* szFormat, Args &&...args) {
std::string strOutput = this->summarizeVariableArguments(std::forward<Args>(args)...);
this->log("INFO: %s", strOutput.c_str());
}
You make another function that accepts va_list to do the job like so:
void cyclOps::Logger::vLog(const char* format, va_list args)
{
std::string logMessage = vFormat<10000>(format, args);
// Do what you want with logMessage
}
template <size_t BufferSize>
std::string cyclOps::Logger::vFormat(const char* format, va_list args)
{
char buffer[BufferSize];
vsprintf(buffer, format, args);
return std::string(buffer);
}
I have tested this on MSVC and GCC for my project. All I can say is it works for me.
Here's a working example. This solution works for C++03 and I believe should work with C++11.
Related
I want simplify the code to use the same function for both ANSI and UNICODE string version
tstring formatW(const std::string format, ...);
tstring formatA(const std::wstring format, ...);
in a single parametrized function
tstring format(const tstring format, ...);
However if I use this as, I get an error in line 3
const std::string fmtA = "MouseWheel nFlags %u,zDelta %d, CPoint(%d,%d";
std::string strA = formatA(fmtA, nFlags, zDelta, pt.x, pt.y ); // Ok
std::string strA2 = format (fmtA, nFlags, zDelta, pt.x, pt.y ); // Fail :(
const std::wstring fmtW = L"MouseWheel nFlags %u,zDelta %d, CPoint(%d,%d";
std::wstring strW = formatW(fmtW, nFlags, zDelta, pt.x, pt.y); // Ok
std::wstring strW2 = format (fmtW, nFlags, zDelta, pt.x, pt.y); // Fail
The error I get is this:
error C2664: ~tstring Format(const tstring,..'.)': cannot convert argument 1 from 'const std::string' to 'const tstring'
I am not familiar with parameteized functions.
char and wchar_t (the underyling types of string/wstring) are not compatible (8 and 16 bits) so you cannot combine them inside a runtime-checking environment.
That is why Windows for example has 2 versions for many functions (MessageBoxA, MessageBoxW etc).
Solution: use a class that stores data as UTF-8 (char) then converts them to 16-bit when wchar_t is needed. Or better, vice versa (uses wchar_t internally and converts data to UTF-8 when a char* is needed).
I'm assuming you have good reasons not to just do the Microsoft #ifdef UNICODE thing? It is a bit rubbish, I admit.
You can't check them at runtime, but this is exactly what c++ compile-time overloading was invented for.
So you probably need to do 3 different things:
1) Write 2 function prototypes that give you the behaviour you need
2) Convert them to take a variadic template argument list instead of ...
3) encapsulate it all in a templated class.
1) To declare and implement the two helper functions with the same name, just do that. The compiler will distinguish them based on the first parameter:
tstring format(const std::string& format, ...);
tstring format(const std::wstring& format, ...);
Of course you then end up writing the same boilerplate code twice, something like:
tstring format(const std::string& format, ...) {
va_list format; /// hmm va_list with references - may need a tweak!
va_start(format, __arg);
return vformatA(format, __arg);
}
And it is a pain writing this twice for each method you merge.
2) To use the variadic templates, Declare and implement your functions as:
template <typename... T>
tstring format(const std::string& format, const T& ... t)
{
return formatA(format, t...);
}
Again you need to write this twice. It isn't too complex compared to the c-style variadics, so you might accept that.
3) A third optional step is to write a Helper template class. So the idea of this is that the actual implementation is performed in a helper class, which you only define for string and wide string. To be honest all it does is add bulk and force you to implement both methods.
You could always use templates and limit them to only string and wstring type, so
template <typename T>
T format(const T format, ...);
and later declare two functions that will do what you want according to the type, so
std::string format<std::string>(const std::string format, ...){...}
std::wstring format<std::wstring>(const std::wstring format, ...){...}
I have a variadic function LogDebug for log writing. Logging happens in two modes.
My application forwards variadic arguments to another variadic function LogDebugEx in most cases hence that path needs to optimize.
To be specific it takes 38% for vsnprintf for some of my requests on callgrind graph. Please note that this function called many times for a single request.
void LogDebug(const char* zFormat, ...)
{
char zDesc[5000];
va_list ap;
va_start(ap, zFormat);
vsnprintf(zDesc, 5000, zFormat, ap); // Need to optimize in remode mode.
va_end(ap);
if (m_logMode == LOG_MODE_LOCAL) // Does not need to optimize this mode.
{
// This mode is not interested.
}
else // m_logMode == LOG_MODE_REMOTE, critical path
{
LogDebugEx("%s", zDesc); // Forwarded to new variadic function
}
}
Question : I need to avoid copying whole argument list to zDesc array before forwarding to LogDebugEx function.
Is there a way i can perfect forward variadic arguments coming to LogDebug into LogDebugEx function?
Any other fancy way to do this would also be fine without changing function calls to LogDebug.
I have C++11 supported compiler GCC 4.9.3.
If we have c++11, why mess around with variadic argument lists?
#include <utility>
extern enum {LOG_MODE_LOCAL, LOG_MODE_REMOTE} m_logMode;
extern void LogDebugEx(const char*, ...);
template<class...Args>
void LogDebug(const char* zFormat, Args&&...args)
{
if (m_logMode == LOG_MODE_LOCAL) // Does not need to optimize this mode.
{
char zDesc[5000];
snprintf(zDesc, 5000, zFormat, args...);
// do what you have to do here
}
else // m_logMode == LOG_MODE_REMOTE, critical path
{
LogDebugEx(zFormat, std::forward<Args>(args)...); // Forwarded to new variadic function
}
}
Let's say I want to have a function overloaded by two versions like this:
a) void query(const string& s); which makes an SQL query to a server.
b) void query(const string& s,...); which builds a query string given by a format string and arguments to be substituted. Internally, this version looks like (I hide the details to not over-complicate the question):
va_list vargs;
va_start(vargs, s);
// ... call vsnprintf to build the query string
// ... call the first version with the query string
va_end(vargs);
Note that I also want this to work in both MSVC and GCC. Of course, by writing as above, I cannot go for the following call because of ambiguity:
query("...");
To resolve the ambiguity in this case, I have tried several ways, but none of them works:
1) Rewrite them as:
void query(const string& s) {
// ...
}
template<typename Value>
void query(const string& s, Value value,...) {
va_list vargs;
va_start(vargs, s);
// ...
}
This compiles and works fine in MSVC, but GCC complains with a warning:
"second parameter of va_start is not last named argument"
Even if I ignore that warning, it doesn't work. Somehow vargs cannot capture value parameter for whatever I try: va_start(vargs, s) or va_start(vargs, value). It seems to me that GCC always takes only unnamed parameters into vargs no matter what we provide as 2nd parameter to va_start.
2) Rewrite them as
void query(const string& s) {
// ...
}
template<typename... Values>
enable_if<(sizeof...(Values) > 0), void>::type
query(const string& s, Values value...) {
va_list vargs;
va_start(vargs, s);
// ...
}
Again, this compiles and works with MSVC. But GCC complains with an error that the 2nd version is a variadic template rather than variadic function, and va_start is not allowed to be used there. It seems that va_start in GCC is built-in rather than from library.
Some people can remark that actually in the 2 versions, 2nd version supersedes the 1st one. That means if I remove the 1st version and put it internally into the 2nd, then everything is alright. But I have a good reason to keep the 1st version: I want the calls with just a string to go directly without unneccessarily calling vsnprintf. So please do not suggest me this way.
I have also thought about combining the 1st version into the 2nd, and then internally count the number of given arguments to know how to go. But it doesn't seem to have a standard way to do that. Determining the number of arguments is possible with variadic templates but not with variadic functions. And if I switch into variadic template, I cannot use va_start anymore in GCC.
Hope someone can help!!
I haven't tested this, but wouldn't the following work?
void query_varargs(const string &s, ...) {
va_list vargs;
va_start(vargs, s);
// ...
}
template<typename... Values>
enable_if<(sizeof...(Values) > 0), void>::type
query(const string& s, Values value...) {
query_varargs(s, ...value);
}
I.e. move the functionality into a different function (query_varargs), then have the variadic template version of query forward to it.
forward argument list from a c++ class member function to a c function, (a wrapper I am creating)
not sure if it is correct? look in the comment inside argsPrinter
// c++ a class function
void argsPrinter( const char *format , ... ){
//markFile(&mApiObj, format , ...); how to pass forward the ... to the c function
/*
va_list args;
va_start (args, format);
markFile(&mApiObj, format , args);
va_end(args);
*/
}
// c function
void markFile(someCustomApi* a, const char *format , ...)
{
FILE *file= fopen(a->somePath, "a");
if(file)
{
va_list args;
va_start (args, format);
vfprintf(file, format, args);
va_end (args);
fclose(file);
}
//else do nothing
}
Edit
the implementation changed, however, I may consider implementing an aditional function, if forward is not allowed to the ...
as markFile(&mApiObj, format , ...);
You can't. If the library containing markFile doesn't provide a markFileV (similar to vsprintf being the counterpart to sprintf), it's just not possible to do this.
Given that you have the source of the C function, you may be able to change that, though.
On a side note (if you have any influence on the C code), this may be a reduced example, but why is markFile first formatting into a buffer (that is PATH_MAX chars long, of all things! What does that have to do with anything?) and then using fprintf to write out the results? Why not just use vfprintf directly?
It's not possible to forward C-style variadic arguments. That's why both fprintf and vfprintf exist. If you need to forward them, you'll have to create your own version of markFile, something like vmarkFile, which would accept a va_list instead of ....
How about just call your C++ function inside implementation of C function?
Such as your C++ function
void argsPrinter( const char *format , ... ){
//markFile(&mApiObj, format , ...); how to pass forward the ... to the c function
/*
va_list args;
va_start (args, format);
markFile(&mApiObj, format , args);
va_end(args);
*/
}
You can implement your C function by calling it:
void markFile(someCustomApi* a, const char *format , ...)
{
a->argsPrinter(format, ... );
//else do nothing
}
Since it is highly ABI dependent you can not do this easily.
Alternatively you can use libffi. Look for variadic function call in the man page.
I still recommend that you write a function that accept a va_list as parameter like vsprintf and co.
Is there any way to directly pass a variable number of arguments from one function to another?
I'd like to achieve a minimal solution like the following:
int func1(string param1, ...){
int status = STATUS_1;
func2(status, param1, ...);
}
I know I can do this using something like the following, but this code is going to be duplicated multiple times so I'd like to keep it as minimalist as possible while also keeping the function call very short
int func1(string param1, ...){
int status = STATUS_1;
va_list args;
va_start(args, param1);
func2(status, param1, args);
va_end(args);
}
Thanks!
No, you have to pass the varargs using a va_list as per your second example.
It's just 3 lines extra code, if you want to avoid duplicating those lines, and func2 is always the same, or atleast takes the same parameters, make a macro out of it.
#define CALL_MY_VA_FUNC(func,param) do {\
va_list args; \
va_start(args,param);\
func(param,param,args);\
va_end(args); } while(0)
Just pass args as a parameter of type va_list to func2
Maybe you could try wrapping the parameters in a struct.
struct Params
{
int status;
std::string param1;
};
void func1(Params& params)
{
int status = params.status;
func2(params);
}
void func2(Params& params)
{
std::string param1 = params.param1;
}
I sometime use that trick when the list of parameter changes a lot during refactoring.
I'm not sure from your question if that could solve your problem though.
--
It is interesting to note that the same thing can be used for templates by defining typedefs and the like in the struct (i usually always use class in my code, since there is basically no difference between struct and class in c++) instead of normal members. It can be a workaround to the problem of having to maintain code with lot of templates that will change a lot during refactoring or developement.