2

I have written a function which tries to do an auto-allocating sprintf by returning a std::string instead of writing into a user-supplied char*. (Please, no answers recommending iostreams or Boost.Format or friends -- I know they exist, I do use them in other contexts, but there is a requirement for this particular case.)

std::string FormatString(const std::string& format, va_list argList)
{
    char smallBuffer[500], *text = smallBuffer;
    int length = _countof(smallBuffer);

    // MSVC is not C99 conformant, so its vsnprintf returns -1
    // on insufficient buffer space
    int outputSize = _vsnprintf(text, length, format.c_str(), argList);
    while (outputSize < 0 && errno == ERANGE && length > 0)
    {
        length <<= 1;
        if (text != smallBuffer) { delete[] text; }
        text = new char[length];
        outputSize = _vsnprintf(text, length, format.c_str(), argList);
    }
    if (outputSize < 0)
    {
        throw std::runtime_error("Failed to format string.");
    }

    std::string ret(text);
    if (text != smallBuffer)
    {
        delete[] text;
    }
    return ret;
}

std::string FormatString(const std::string& format, ...)
{
    va_list argList;
    va_start(argList, format);

    std::string result;
    try
    {
        result = FormatString(format, argList);
    }
    catch(...)
    {
        va_end(argList);
        throw;
    }
    va_end(argList);

    return result;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int foo = 1234;
    std::string bar = "BlaBla";
    std::cout << FormatString("%i (%s)", foo, bar.c_str()) << std::endl;
    return 0;
}

(And yes, I see the irony of piping a C-formatted string to a C++ iostream. This is just test code.)

Unfortunately, using VS2008, it's crashing deep within the bowels of the printf internals, apparently because it's reading the wrong arguments out of the va_list (according to the debugger, after the va_start it's pointing at a four-byte null sequence immediately prior to the "real" first parameter).

Of particular note is that if in the variadic function I change the const std::string& format to just std::string format (ie. pass by value), it works properly; it also does so if I change it to a const char *, of course.

Is this some sort of compiler bug, or is it not legal to use a va_list with reference parameters?

Miral
  • 12,637
  • 4
  • 53
  • 93
  • [Are there gotchas using varargs with reference parameters](http://stackoverflow.com/questions/222195/are-there-gotchas-using-varargs-with-reference-parameters). – Jesse Good Feb 03 '12 at 00:12
  • Thanks, there's a good explanation there too. Shame that didn't come up in my pre-query search. :) – Miral Feb 03 '12 at 00:16

1 Answers1

3

I think you are out of luck if you want to pass a reference. Here is what the C++2011 standard has to say about the subject in 18.10 [support.runtime] paragraph 3:

The restrictions that ISO C places on the second parameter to the va_start() macro in header are different in this International Standard. The parameter parmN is the identifier of the rightmost parameter in the variable parameter list of the function definition (the one just before the ...).230 If the parameter parmN is declared with a function, array, or reference type, or with a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Thanks, that's what I suspected. Although I'm not sure what "the type that results when passing an argument for which there is no parameter" means. – Miral Feb 03 '12 at 00:12
  • I'd think this refers to the arguments you pass for which there is no parameter name, i.e. those passed in the variable argument list. Since there are some type conversions (e.g `float` to `double`) this essentially refers to the cases where you try to pass something which you can't pass through a variable length argument list. – Dietmar Kühl Feb 03 '12 at 00:24