A small printf-like function using variadic templates I'm looking to get some feedback on a small printf-like function I wrote that provides simplified behavior similar to boost::format:

#ifndef EXT_FORMAT_HPP__
#define EXT_FORMAT_HPP__
// ext::format
// Implements a variadic printf-like function providing behavior similar to
// boost::format
#include &lt;regex&gt;
#include &lt;sstream&gt;
#include &lt;string&gt;
#include &lt;utility&gt;

namespace {

inline std::string format_helper(
    const std::string &amp;string_to_update, 
    const size_t) {
  return string_to_update;
}

template &lt;typename T, typename... Args&gt;
inline std::string format_helper(
    const std::string &amp;string_to_update, 
    const size_t index_to_replace, 
    T &amp;&amp;val,
    Args &amp;&amp;...args) {  
  std::regex pattern{"%" + std::to_string(index_to_replace)};
  std::string replacement_string{(std::ostringstream{} &lt;&lt; val).str()};
  return format_helper(
      std::regex_replace(string_to_update, pattern, replacement_string), 
      index_to_replace + 1, 
      std::forward&lt;Args&gt;(args)...);
}

} // namespace


namespace ext {

template &lt;typename... Args&gt;
inline std::string format(const std::string &amp;format_string, Args &amp;&amp;...args) {
  return format_helper(format_string, 1, std::forward&lt;Args&gt;(args)...);
}

} // namespace ext

#endif


Usage example:

#include "format.hpp"
#include &lt;iostream&gt;

struct foo {
  int value;
  foo(int val) : value{val} {
  }
};

std::ostream &amp;operator&lt;&lt;(std::ostream &amp;os, const foo &amp;f) {
  os &lt;&lt; "foo(" &lt;&lt; f.value &lt;&lt; ")";
  return os;
}

int main() {
  double tmp = 37.382;
  std::cout &lt;&lt; ext::format("%1 + %2 * %1 = %3", 5, tmp, 5 + tmp * 5) &lt;&lt; std::endl;

  // Support user defined types provided the appropriate operator&lt;&lt; overload is defined.
  foo a_foo(55);
  std::cout &lt;&lt; ext::format("Here is a foo constructed with 55: %1", a_foo) &lt;&lt; std::endl;
};


This is not written to replace boost::format; it's intended to be used as a header-only include with a very limited scope. As currently written, it is possible to supply too few or too many arguments to the parameter pack. Passing too few arguments will return a string that still has format specifiers. Passing excess arguments are simply ignored and you pay the price of excess calls to std::regex_replace, which will do nothing. With that in mind, I'm looking for any and all feedback.
I find this to be a sweet demo, but I don't like the traps in it for the unwary. Like you mention, there is little validation of parameter counts. There is also a chance that the same location can be replaced multiple times, e.g. with a call to ext::format("%1", "%2", "%3, "%4", "hi!") resulting in "hi!". Perhaps you might consider that a bonus.

What I find most surprising in the code is this:


std::string replacement_string{(std::ostringstream{} &lt;&lt; val).str()};



I would probably have tended towards auto replacement_string{std::to_string(val)}; or, more likely, just embedded that in the call to regex_replace. Do you do this for compatibility with existing code that provides an overload for operator&lt;&lt; but not to_string?

Finally, I'm torn about the use of regex. It seems like a pretty big hammer without a lot of need, here, however it does keep the code short and sweet, and implicitly avoids an infinite loop that a flawed implementation might have with ext::format("%1", "%1").

Reviewing the tests, I find the Does_Not_Alter_Format_String test to be surprising as well. I would have thought the const qualifier would indicate this well enough.