Yuriy Romanenko
Yuriy Romanenko

Reputation: 1454

Is there a more elegant way of marrying sprintf and std::string in C++?

Very frequently in my C++ code I use the following type of helper function:

static inline std::string stringf(const char *fmt, ...)
{
    std::string ret;
    // Deal with varargs
    va_list args;
    va_start(args, fmt);
    // Resize our string based on the arguments
    ret.resize(vsnprintf(0, 0, fmt, args));
    // End the varargs and restart because vsnprintf mucked up our args
    va_end(args);
    va_start(args, fmt);
    // Fill the string
    if(!ret.empty())
    {
        vsnprintf(&ret.front(), ret.size() + 1, fmt, args);
    }
    // End of variadic section
    va_end(args);
    // Return the string
    return ret;
}

It has a few upsides:

  1. No arbitrary limits on string lengths
  2. The string is generated in-place and doesn't get copied around (if RVO works as it should)
  3. No surprises from the outside

Now, I have a few of problems with it:

  1. Kind of ugly with the rescanning of the varargs
  2. The fact that std::string is internally a contiguous string with space for a null terminator directly after it, does not seem to actually be explicitly stated in the spec. It's implied via the fact that ->c_str() has to be O(1) and return a null-terminated string, and I believe &(data()[0]) is supposed to equal &(*begin())
  3. vsnprintf() is called twice, potentially doing expensive throw-away work the first time

Does anybody know a better way?

Upvotes: 5

Views: 2367

Answers (3)

borisbn
borisbn

Reputation: 5054

As you added the tag c++11 I suppose that you can use it. Then you can simplify your code to this:

namespace fmt {

template< class ...Args >
std::string sprintf( const char * f, Args && ...args ) {
    int size = snprintf( nullptr, 0, f, args... );
    std::string res;
    res.resize( size );
    snprintf( & res[ 0 ], size + 1, f, args... );
    return res;
}

}

int main() {
    cout << fmt::sprintf( "%s %d %.1f\n", "Hello", 42, 33.22 );
    return 0;
}

http://ideone.com/kSnXKj

Upvotes: 5

Nir Friedman
Nir Friedman

Reputation: 17714

Are you married (pun intended) to std::sprintf()? If you are using C++ and the oh so modern std::string, why not take full advantage of new language features and use variadic templates to do a type safe sprintf that returns a std::string?

Check out this very nice looking implementation: https://github.com/c42f/tinyformat. I think this solves all your issues.

Upvotes: 5

o11c
o11c

Reputation: 16146

Don't do the first vsnprintf with size 0. Instead, use a stack buffer with some probable size (e.g. something between 64 to 4096), and copy that into the return value if it fits.

Your concerns about std::string's contiguity are misplaced. It is well-defined and perfectly okay to rely on.

Finally - I would like to reecho that a compile-time format-checking library would be better. You need C++14 for full power, but C++11 supports enough that you should be able to #ifdef some header code without losing any expressive. Remember to think about gettext though!

Upvotes: 1

Related Questions