12

I'm trying to create a function that takes a variable amount of std::string arguments and formats a string with it.

Example:

Test::formatLine(const string::format, ...)
{
    const std::string buffer;
va_list args;
va_start(args, format);
vsprintf(buffer.c_str, format.c_str, args);
va_end(args);
cout << buffer << endl;
}

Compiling this snippet errors:

Error   1   error C3867: 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str': function call missing argument list; use '&std::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str' to create a pointer to member

What I want to achieve:

Test t = Test();
t.formatLine("Hello %s!", "monsieur");

should print Hello monsieur!

t.formatLine("Hello %s %s! How %s you today?", "good", "sir", "are");

should print Hello good sir! How are you today?

Is it even possible to use va_list and vsprintf with std::string only, avoiding char buffer[size]?

Working example (so far) with fixes suggested by Igor, using buffer:

void Test::formatLine(string format, ...)
{
    char buffer[256];
    va_list args;
    va_start(args, format);
    vsprintf_s(buffer, format.c_str(), args);
    va_end(args);
    cout << buffer << endl;
}

Using Igor Tandetnik's suggestion and sample code I finally got a working example that does not use char buffer[size]:

void Test::formatLine(string format, ...)
{
    vector<char> buf(256);
    va_list args;
    va_start(args, format);
    vsnprintf_s(&buf[0], buf.size(), buf.size() + strlen(format.c_str()), format.c_str(), args);
    va_end(args);
    cout << &buf[0] << endl;
}
5
  • 3
    Only trivial types are allowed to be passed to ..., std::string is no such type. Commented Sep 25, 2013 at 16:00
  • @Xeo But he is only passing trivial types in his examples :) Of course, you're right, this will fail horribly if you change the example to t.formatLine("Hello %s!", std::string("monsieur")); Commented Sep 25, 2013 at 16:10
  • Your "working example" is good as long as the resulting string does fit into 256 characters. Also, it is using a Microsoft-specific, non-portable vsprintf_s function; this might or might not be a concern for you. Commented Sep 25, 2013 at 16:29
  • Why buf.size() + strlen(format.c_str())? This doesn't make any sense. I suggest you pass _TRUNCATE as the third parameter of vsnprintf_s. And of course, you still have a problem if the resulting string is longer than 256 characters. Commented Sep 25, 2013 at 16:33
  • I'm trying to make the resulting string size dynamic - out of curiosity - 256 should be more than enough for me right now. The non-portable function is not a concern. Commented Sep 25, 2013 at 16:36

2 Answers 2

14

A Production Quality Answer

#include <cstdarg>
#include <string>
#include <vector>

// requires at least C++11
const std::string vFormat(const std::string sFormat, ...) {

    const char * const zcFormat = sFormat.c_str();

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, sFormat);

    // reliably acquire the size from a copy of
    // the variable argument array
    // and a functionally reliable call
    // to mock the formatting
    va_list vaCopy;
    va_copy(vaCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaCopy);
    va_end(vaCopy);

    // return a formatted string without
    // risking memory mismanagement
    // and without assuming any compiler
    // or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), zc.size()); } 

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() { 

    std::time_t t = std::time(nullptr);
    int i1 = 11; int i2 = 22; int i3 = 33;
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << vFormat(" [%s]: %s {i1=%d, i2=%d, i3=%d}",
                "DEBUG",
                "Xyz failed",
                i1, i2, i3)
        << std::endl;
    return 0; }
Sign up to request clarification or add additional context in comments.

Comments

4

First, it's buffer.c_str() and format.c_str() (note the parentheses).

Second, the first parameter of vsprintf should be a modifiable buffer of sufficient size. You are trying to pass a const char* pointing to a buffer just one byte large.

You could use vector<char> as a buffer holder (it's easy to resize). The problem is, there's no way to get the required buffer size out of vsprintf. One technique is to allocate some initial buffer, then call vsnprintf (note the 'n') repeatedly, doubling the size of the buffer every time the function says it's too small.

8 Comments

Could you maybe give me an example on how to use vector<char> in this case? I just can't figure out how to use it with vsnprintf().
vector<char> buf(1024); vsnprintf(&buf[0], buf.size(), format.c_str(), args);. Resizing the buffer is left as an exercise for the reader.
@phew vsnprintf is the right idea, but you shouldn't have to call it repeatedly. Make buffer a vector<char> and call vsnprint(&buffer[0], 0, format.c_str(), args);. If the return value is non-negative, call buffer.resize(retval+1); and then vsnprintf(&buffer[0], retval+1, format.c_str(), args);
@Praetorian: Interesting. I was going by MSDN documentation, which states that vsnprintf returns the number of characters written if successful, and -1 when buffer is too small. I've now checked the C99 standard, which says that vsnprintf returns the required size of the buffer, and thus can be used to measure it in advance. I wonder (but am too lazy to check) whether MSVC implementation is non-conforming, or whether the documentation is wrong.
@Praetorian: Also, per C99, the first parameter may be NULL if the second is 0, so one can do vsnprint(NULL, 0, format.c_str(), args);. This removes the concern of doing &buf[0] on a zero-sized vector. Also, one shouldn't pass the same va_list value twice to two vsnprintf calls; per 7.15p3, the value becomes indeterminate after the first call. One must use va_start again, or else make a copy with va_copy beforehand.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.