是否有任何优雅的方式在C ++中格式化字符串?

问题描述 投票:0回答:1

我真的不想写这样难看的代码,如果您有更好的方法,请帮助我进行优化。我真的不想为向量大小写更多的情况,这很愚蠢。

template<typename ... Args>
std::string string_format(const std::string &format, Args... args)
{
    size_t size = 1 + snprintf(nullptr, 0, format.c_str(), args ...);

    char bytes[size] = {0};
    snprintf(bytes, size, format.c_str(), args ...);
    return std::string(bytes);
}

std::string test_format(const std::string& str, const std::vector<std::string>& vStr)
{
    switch (vStr.size())
    {
    case 1:
        return string_format(str, vStr[0]);
        break;
    case 2:
        return string_format(str, vStr[0], vStr[1]);
        break;
    case 3:
        return string_format(str, vStr[0], vStr[1], vStr[2]);
        break;
    case 4:
        return string_format(str, vStr[0], vStr[1], vStr[2], vStr[3]);
        break;
    case 5:
        return string_format(str, vStr[0], vStr[1], vStr[2], vStr[3], vStr[4]);
        break;
    case 6:
        return string_format(str, vStr[0], vStr[1], vStr[2], vStr[3], vStr[4], vStr[5]);
        break;
    // ....

    default:
        return str;
        break;
    }
}
c++
1个回答
2
投票

正如评论中的人所建议,为了帮助清理格式,最好使用适当的格式库,例如libfmt(现在在C ++ 20标准中也有效)

但是,如果您的要求是您拥有运行时 std::vector<std::string>,并且打算将其转换为compile-time可变参数模板参数序列,那么库可能无法解决此问题您。

由于您要传递包含运行时值的向量,因此在某种程度上需要一种将其转换为编译时列表的方法,以便可以将其传递给string_formatis可能,但是需要一些基础结构。

由于可变参数是编译时的效果,所以我们不能简单地将args转换为args...。我们至少必须知道我们可以转换成多少个函数的上限。但是,至少可以使用模板来实现。

想法是在compile-time上建立一个跳转图(实际上是一个切换用例),并在运行时对其进行引用。这将要求您必须对可以传递给string_format的参数数量设置任意限制。


constexpr auto limit = 32; // let's make 32 arguments the arbitrary max

using converter = std::string(*)(const std::string&, const std::vector<std::string>>&);

template <std::size_t...Idxs>
std::string format_vector_impl(const std::string& format, const std::vector<std::string>>& args, std::index_sequence<Idxs...>)
{
    return format_string(format, args[Idxs]...);
}

template <std::size_t I>
std::string format_vector(const std::string& format, const std::vector<std::string>>& args)
{
    return format_vector_impl(format, args, std::make_index_sequence<I>{});
}

template <std::size_t...Idxs>
constexpr std::array<converter,sizeof...(Idxs)> make_converter_map(std::index_sequence<Idxs...>)
{
    return {&format_vector<Idxs>...};
}

std::string test_format(const std::string& format, const std::vector<std::string>>& args)
{
    static constexpr auto s_converters = make_converter_map(std::make_index_sequence<limit>{});

    assert(args.size() < limit); // if this ever gets triggered, change 'limit'

    // use 'args.size()' as the index into the function
    return s_converter[args.size()](frmat, args);
}

此解决方案至少使用C ++ 14。

其工作方式如下:

  • 通过std::string(*)(const std::string&, const std::vector<std::string>&)将所有转换标准化为format_vector的签名
  • format_vector<I>委托给format_vector_impl<Idxs...>
  • 使用Idxs...,索引的编译时列表来解压缩参数向量
  • 根据规范化的函数签名构建数组
  • 使用args.size()作为键调用该功能。

通过使用这种方法,您可以使参数的数量与您想要/需要的一样大。


注:使用类似libfmt的库将有助于解决您的代码当前具有的一些未定义行为,其中args...正在将std::string值传递给snprintfsnprintf使用C样式...变量而不是可变参数template参数,因此它无法理解或使用C ++类型-仅原始类型(如整数值和const char*)。

Edit:我刚刚注意到的另一件事是,您当前的代码使用可变长度数组(VLA),它们是not标准C ++。数组需要在编译时固定。在标准C ++中,这不能在运行时完成,但是可以通过某些编译器扩展来完成。我在这里指的是string_format函数:

    size_t size = 1 + snprintf(nullptr, 0, format.c_str(), args ...);

    char bytes[size] = {0};

为此,我建议使用std::vector<char>或什至仅使用std::string并使用计算的大小调整大小。

© www.soinside.com 2019 - 2024. All rights reserved.