Reputation: 10064
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+
.
EDIT: This needs to work without C++11, but I'm still interested in C++11 solutions to the problem!
Upvotes: 1
Views: 298
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
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
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