【问题标题】:How to create a variadic template string formatter如何创建可变参数模板字符串格式化程序
【发布时间】:2014-07-07 16:40:39
【问题描述】:

我们需要一直格式化字符串。能这么说就太好了:

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

有没有 C++ 方法可以做到这一点?我考虑过的一些替代方案:

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

既然我们有了 C++11,难道没有更好的可变参数模板方法吗?

【问题讨论】:

  • @ruslo 似乎无法自动生成 std​​::string 结果。此外,所需的语法太奇怪了,无法在整个项目中使用。

标签: c++ templates c++11 string-formatting variadic-templates


【解决方案1】:

它当然可以用可变参数模板用 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();
}

您可以按照自己的方式使用它:

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

如果您不想使用 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());
}

snprintfboost::format 在格式规范方面存在一些差异,但您的示例适用于两者。

【讨论】:

  • 您的所有代码都丢失了std::forwardstd::move,并制作了不必要的formatString 副本
  • 作为替代方案(不是更正),有一个技巧可以压缩所有内容并完全避免递归,但 clang 抱怨未使用的局部变量:coliru.stacked-crooked.com/a/ed07c49c0d53266f
  • 我收回了,boost 格式需要const string&amp;,所以不清楚是否有不必要的副本。我已经更改了我的版本,显然不会复制:coliru.stacked-crooked.com/a/9a00e97261e3c39e
  • @isarandi “它当然可以用可变参数模板用 C++11 编写”。您能否提供有关此 C++ 11 解决方案的答案?
  • @doofx 好吧,这就是答案本身:)
【解决方案2】:

fmt library 完全实现了这一点,使用可变参数模板进行字符串格式化。示例:

// printf syntax:
std::string formattedStr = fmt::sprintf("%s_%06d.dat", "myfile", 18);

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

免责声明:我是图书馆的作者。

【讨论】:

    猜你喜欢
    • 2020-01-09
    • 2014-11-08
    • 2022-11-22
    • 2021-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-04
    • 1970-01-01
    相关资源
    最近更新 更多