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.
operator <<will not crash the same as printf if you don't give it the good parameters