Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a variadic template string formatter

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

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

Is there a C++ way of doing this? Some alternatives I considered:

  • snprintf: uses raw char buffers. Not nice in modern C++ code.
  • std::stringstream: does not support format pattern strings, instead you must push clumsy iomanip objects into the stream.
  • boost::format: uses an ad-hoc operator overload of % to specify the arguments. Ugly.

Isn't there a better way with variadic templates now that we have C++11?

like image 764
isarandi Avatar asked May 19 '14 16:05

isarandi


2 Answers

It can certainly be written in C++11 with variadic templates. It's best to wrap something that already exists than to try to write the whole thing yourself. If you are already using Boost, it's quite simple to wrap boost::format like this:

#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 this the way you wanted:

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

If you don't want to use Boost (but you really should) then you can also wrap snprintf. It is a bit more involved and error-prone, since we need to manage char buffers and the old style non-type-safe variable length argument list. It gets a bit cleaner by 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 format specification but your example works with both.

like image 145
isarandi Avatar answered Sep 25 '22 15:09

isarandi


The fmt library implements exactly that, string formatting using variadic 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'm the author of the library.

like image 37
vitaut Avatar answered Sep 24 '22 15:09

vitaut