How to create a line formatter for a variation template string

We need to format the lines all the time. It would be so nice to say:

std::string formattedStr = format("%s_%06d.dat", "myfile", 18); // myfile_000018.dat 

Is there a C ++ way? Some alternatives that I have been considering:

  • snprintf : uses raw char buffers. I do not like it in modern C ++ code.
  • std::stringstream : does not support formatting pattern strings, you should instead insert clumsy iomanip objects into the stream.
  • boost::format : uses the special overload of the % operator to specify arguments. Ugly.

Is there a better way with variation templates now that we have C ++ 11?

+8
c ++ c ++ 11 templates string-formatting variadic-templates
source share
2 answers

This, of course, can be written in C ++ 11 with variable templates. It’s best to wrap what already exists than to try to write everything yourself. If you are already using Boost, it is quite simple to wrap boost::format as follows:

 #include <boost/format.hpp> #include <string> namespace details { boost::format& formatImpl(boost::format& f) { return f; } template <typename Head, typename... Tail> boost::format& formatImpl( boost::format& f, Head const& head, Tail&&... tail) { return formatImpl(f % head, std::forward<Tail>(tail)...); } } template <typename... Args> std::string format( std::string formatString, Args&&... args) { boost::format f(std::move(formatString)); return details::formatImpl(f, std::forward<Args>(args)...).str(); } 

You can use it the way you want:

 std::string formattedStr = format("%s_%06d.dat", "myfile", 18); // myfile_000018.dat 

If you don't want to use Boost (but you really need to), you can also wrap snprintf . This is a bit more attractive and error prone, since we need to manage the char buffers and the old argument of non-type variable length arguments. It gets a little cleaner using unique_ptr 's:

 #include <cstdio> // snprintf #include <string> #include <stdexcept> // runtime_error #include <memory> // unique_ptr namespace details { template <typename... Args> std::unique_ptr<char[]> formatImplS( size_t bufSizeGuess, char const* formatCStr, Args&&... args) { std::unique_ptr<char[]> buf(new char[bufSizeGuess]); size_t expandedStrLen = std::snprintf(buf.get(), bufSizeGuess, formatCStr, args...); if (expandedStrLen >= 0 && expandedStrLen < bufSizeGuess) { return buf; } else if (expandedStrLen >= 0 && expandedStrLen < std::numeric_limits<size_t>::max()) { // buffer was too small, redo with the correct size return formatImplS(expandedStrLen+1, formatCStr, std::forward<Args>(args)...); } else { throw std::runtime_error("snprintf failed with return value: "+std::to_string(expandedStrLen)); } } char const* ifStringThenConvertToCharBuf(std::string const& cpp) { return cpp.c_str(); } template <typename T> T ifStringThenConvertToCharBuf(T const& t) { return t; } } template <typename... Args> std::string formatS(std::string const& formatString, Args&&... args) { // unique_ptr<char[]> calls delete[] on destruction std::unique_ptr<char[]> chars = details::formatImplS(4096, formatString.c_str(), details::ifStringThenConvertToCharBuf(args)...); // string constructor copies the data return std::string(chars.get()); } 

There are some differences between snprintf and boost::format in terms of the format specification, but your example works with both.

+11
source share

The fmt library implements just that, formatting strings using variable templates. Example:

 // printf syntax: std::string formattedStr = fmt::sprintf("%s_%06d.dat", "myfile", 18); // Python-like syntax: std::string formattedStr = fmt::format("{}_{:06}.dat", "myfile", 18); 

Disclaimer I am the author of the library.

0
source share

All Articles