QuestionC
QuestionC

Reputation: 10064

What is the best way to build a C++ string using C format strings?

I want to add to my company's code a std::string fmtString(char const * fmt, ...); function which lets me build C++ strings from C objects in a printf way. I really liked the readability of C format strings and would like to bring it over instead of the C++ style of using operator<< or operator+.

  1. Is this a bad / inefficient idea?
  2. Does this already exist, or is C-style building of C++ strings already easy without a custom method?
  3. What's the best way to implement such a method? It would have to be thread safe.

EDIT: This needs to work without C++11, but I'm still interested in C++11 solutions to the problem!

Upvotes: 1

Views: 298

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275946

template<class...Args>
std::string fmt( string_view, Args&&... )

is a C++11 signature for such a function. This allows run time type safety and controlled failure.

Do not use C style VA-args, as that makes any kind of sensible failure impossible (type checking, arg count checking, etc).

Parsing said string_view will take time at runtime, but other than that there is no fundamental inefficiency here. A constexpr parser that turns a string of characters into a parsed formatter may be a good idea, possibly a string-literal.

boost has many C-style C++ formatters.

You'll want to walk the format string, looking for format commands. Dump the non-format text as you go along. At each format command, switch on it and consume one or more Args into an error-generating consumer of the type you expect.

An example of a %d consumer would be:

template<class T> struct simple_printer;

template<> struct simple_printer {
  void operator()( std::string& s, int x ) const {
    // TODOL print x to s
  }
};
template<class X, class... Ts>
std::false_type
simple_print(std::false_type, Ts&&...) { return {}; }

template<class X, class...Args>
std::true_type
simple_print(std::true_type, std::string& out, Args&&... in ) {
  simple_printer<X>{}( out, std::forward<T>(t) );
}

then in the parsing code, when you run into %d, you do:

if (fmt_code == "%d") {
  auto worked = simple_print( std::is_convertible<Arg0, int>{}, out, std::forward<Arg0>(arg0) );
  if (!worked) {
    // report error then
    return;
  }
}

after you parse each format command, you recurse on your print function with less arguments and the tail end of the format string.

Handling more complex formatting (like %.*f) is naturally trickier. It could help if you request that "formatting bundle" arguments are grouped in the Args somehow.

Upvotes: 2

fjardon
fjardon

Reputation: 7996

Not judging if it is a good idea or not, you could use vsnprintf to implement your function.

The return value of this function will help you determine the size of the needed buffer.

Upvotes: 0

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385395

This breaks type safety, a feature that was one of the major benefits of moving from C to C++ in the first place. If you send the wrong data, you get runtime crashes or unexpected output, rather than nice cosy compilation failures.

Try Boost.Format, which does what you're doing, but better:

The format library provides a class for formatting arguments according to a format-string, as does printf, but with two major differences :

  • format sends the arguments to an internal stream, and so is entirely type-safe and naturally supports all user-defined types.
  • The ellipsis (...) can not be used correctly in the strongly typed context of format, and thus the function call with arbitrary arguments is replaced by successive calls to an argument feeding operator%

It's thread-safe if you follow the one-instance-per-thread model.

You could improve your own approach by swapping the varargs for variadic templates, but why re-invent the wheel?

Upvotes: 1

Related Questions