如何创建可变参数模板字符串格式化程序

isa*_*ndi 8 c++ templates string-formatting variadic-templates c++11

我们需要一直格式化字符串.能说:真是太好了

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

有这样的C++方式吗?我考虑过一些替代方案

  • snprintf:使用原始char缓冲区.在现代C++代码中不太好用.
  • std::stringstream:不支持格式模式字符串,而是必须将笨拙的iomanip对象推送到流中.
  • boost::format:使用ad-hoc运算符重载%来指定参数.丑陋.

现在我们有C++ 11,对于可变参数模板有没有更好的方法?

isa*_*ndi 12

它当然可以使用可变参数模板在C++ 11中编写.最好包装已存在的东西,而不是试图自己编写整个东西.如果您已经在使用Boost,那么boost::format像这样包装非常简单:

#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();
}
Run Code Online (Sandbox Code Playgroud)

你可以按照你想要的方式使用它:

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

如果你不想使用Boost(但你真的应该),那么你也可以换行snprintf.由于我们需要管理char缓冲区和旧式非类型安全可变长度参数列表,因此它更容易涉及并且容易出错.通过使用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());
}
Run Code Online (Sandbox Code Playgroud)

有一些区别snprintf,并boost::format在格式规范的方面,而且你的例子两者的工作原理.