2

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?

John Fitzpatrick
  • 4,207
  • 7
  • 48
  • 71
  • I don't think you can do that. You have to parse and repackage the list first. – Mike Dec 06 '12 at 17:12
  • You can't. For logging I suggest you instead make use of the output stream paradigm, using the `<<` operator to chain the output together. Of course it won't be possible in a C program, only in C++. – Some programmer dude Dec 06 '12 at 17:14
  • Your inner functions should all take a `va_list`. Only the outer layers should use `...` and then call the `va_list` versions because you can't pass on one `...` set to another. This is also why functions like `vfprintf`, `vsprintf`, etc. exist. – melpomene Dec 06 '12 at 17:14
  • @melpomene: If I understand you correctly I will need to leave the first two lines in each of the half dozen functions as they are, but I can package the third and fourth line in a single function that takes the `va_list` argument and returns `std::string` – John Fitzpatrick Dec 06 '12 at 17:16
  • Why not use a multiline macro? – user93353 Dec 06 '12 at 17:23
  • 1
    You have to plan for that in advance and design all lower-level functions in terms of `va_list` argument, not `...` argument: http://stackoverflow.com/questions/5383642/to-invoke-a-variadic-function-with-unamed-arguments-of-another-variadic-function – AnT stands with Russia Dec 06 '12 at 17:30
  • @Joachim Pileborg: Your proposal of using an output stream with `<<` is a longer range goal for me. I'm transitioning from C amateur to C++ amateur and haven't learned operator overloading yet. – John Fitzpatrick Dec 06 '12 at 17:35
  • @AndreyT OMG my questions a dupe, voting to close. – John Fitzpatrick Dec 06 '12 at 17:36
  • 1
    Btw your code is missing the `va_end`, so really it's 5 lines in common that you can bring down to 4 by writing a common function that takes a `va_list`. varargs are in some ways a bit not-very-good. In this case you already *are* commoning up most of the work in a call to a function that takes a va_list (`_vsnprintf_s`), so there's not much left to gain by that route. – Steve Jessop Dec 06 '12 at 18:03

3 Answers3

4

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).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
4

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());
}
Community
  • 1
  • 1
hate-engine
  • 2,300
  • 18
  • 26
3

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.

Vite Falcon
  • 6,575
  • 3
  • 30
  • 48
  • 1
    Despite the doomsayers in the other answers, this approach has been valid C for forty years. – Pete Becker Dec 06 '12 at 17:53
  • I have no idea why @Basile says there is no portable solution. But with my limited experience, this should be portable, like you said. But maybe I'm missing something and hence my phrase 'It works for me'. – Vite Falcon Dec 06 '12 at 18:01
  • Oh yea. I totally appreciate the backup :) – Vite Falcon Dec 06 '12 at 19:14