17

In my application I have a lot of logs. I do accumulate all errors from all logs in one place called errorsLogger. I've implemented it this way:

static Logger errorsLogger;

....

void Logger::Error(std::string format, ...) {
    va_list arglist;
    va_start(arglist, format);

    if (this != &errorsLogger) {
        errorsLogger.Error(format, arglist);      // how to forward parameters?
    }

    vfprintf(logFile, , format.c_str(), arglist);
    fprintf(logFile, "\n");

    fflush(logFile);
    va_end( arglist );
}

However this code doesn't work as expected errorsLogger contains a little bit strange strings - it seems variable arguments was not passed. How to fix my code to be valid?

Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305
  • 1
    You need a version of `Error` that takes a [`va_list`](http://en.cppreference.com/w/cpp/utility/variadic/va_list). – BoBTFish May 08 '13 at 18:48
  • 1
    Consider changing interface of your logger so it's looking like `ostream`, you will be able to do things like: `Logger::error() << "the variable x is " << x;` In fact `Logger::error()` can return an `ostream&` – piokuc May 08 '13 at 18:48
  • can I just format a string as a first step and then just forward std::string? – Oleg Vazhnev May 08 '13 at 19:03

3 Answers3

29

The typical formulation of this in C is to have two functions, one that accepts ... and one that accepts a va_list (e.g., printf versus vprintf). In C++ it’s convenient to do this with overloads:

// public
void Logger::Error(const std::string& format, ...) {
    va_list args;
    va_start(args, format);
    Error(format, args);
    va_end(args);
}

// private
void Logger::Error(const std::string& format, va_list args) {
    if (this != &errorsLogger)
        errorsLogger.Error(format, args);

    vfprintf(logFile, format.c_str(), args);
    fprintf(logFile, "\n");
    fflush(logFile);
}

Using C++11, it is possible to do directly with a variadic template. You can also forward arguments to C-style variadic functions.

template<class... Args>
void Logger::Error(const std::string& format, Args&&... args) {    
    if (this != &errorsLogger)
        errorsLogger.Error(format, std::forward<Args>(args)...);

    fprintf(logFile, format.c_str(), std::forward<Args>(args)...);
    fprintf(logFile, "\n");
    fflush(logFile);
}
Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
5

In short, you can't.

All you can do is write an equivalent member function that takes a va_list instead of variable arguments, and pass the initialized va_list down.

0

For that to work, Logger::Error would have to be declared to accept a va_list as a parameter, much like vfprintf, rather than variable arguments in the form of ... like fprintf.

James Holderness
  • 22,721
  • 2
  • 40
  • 52