创建一个编译时字符串重复一个字符 n 次

Creating a compile time string repeating a char n times

我正在使用这样的函数在 xml 文件中导出数据(注意:愚蠢的例子):

void write_xml_file(const std::string& path)
{
    using namespace std::string_view_literals; // Use "..."sv

    FileWrite f(path);
    f<< "<root>\n"sv
     << "\t<nested1>\n"sv
     << "\t\t<nested2>\n"sv
     << "\t\t\t<nested3>\n"sv
     << "\t\t\t\t<nested4>\n"sv;
     //...
}

那些 << 接受 std::string_view 论点的地方:

FileWrite& FileWrite::operator<<(const std::string_view s) const noexcept
   {
    fwrite(s.data(), sizeof(char), s.length(), /* FILE* */ f);
    return *this;
   }

如有必要,我可以使用 std::stringstd::array、...

添加重载

现在,我真的很想这样写上面的内容:

// Create a compile-time "\t\t\t..."sv
consteval std::string_view indent(const std::size_t n) { /* meh? */ }

void write_xml_file(const std::string& path)
{
    using namespace std::string_view_literals; // Use "..."sv

    FileWrite f(path);
    f<< "<root>\n"sv
     << indent(1) << "<nested1>\n"sv
     << indent(2) << "<nested2>\n"sv
     << indent(3) << "<nested3>\n"sv
     << indent(4) << "<nested4>\n"sv;
     //...
}

有没有人可以提示我如何实现 indent()? 我不确定我 return a std::string_view 指向编译时分配的静态常量缓冲区的想法是否最合适,我愿意接受其他建议。

使用 std::string (size_t n, char c);

另见 create-string-with-specified-number-of-characters

void write_xml_file(const std::string& path)
{

 using namespace std::string_view_literals; // Use "..."sv

 FileWrite f(path);
 f<< "<root>\n"sv
 << std::string ( 1, '\t') << "<nested1>\n"sv
 << std::string ( 2, '\t') << "<nested2>\n"sv
 << std::string ( 3, '\t') << "<nested3>\n"sv
 << std::string ( 4, '\t') << "<nested4>\n"sv;
 //...
}

如果你想让indent在编译时工作,那么你需要N也是一个编译时值,对于indent 作为 constexpr 子表达式的一部分被调用。

因为这是为了流式传输到某些文件支持的流对象 FileWrite,后者已过时——这意味着您需要 N 在编译时(例如将其作为模板参数传递)。

这会将您的签名更改为:

template <std::size_t N>
consteval auto indent() -> std::string_view

问题的第二部分是你想要这个return一个std::string_view。这里的复杂之处在于 constexpr 上下文不允许 static 变量——因此您在上下文中创建的任何内容都将具有 automatic 存储持续时间。从技术上讲,您不能只是简单地在函数中创建一个数组,然后 return 它的 string_view - 因为这会由于存储空间不足而导致悬空指针(从而导致 UB) -of-scope 在函数的末尾。所以你需要解决这个问题。

最简单的方法是使用包含 static 数组的 structtemplate(在本例中为 std::array,因此我们可以 return它来自一个函数):

template<std::size_t N>
struct indent_string_holder
{
    // +1 for a null-terminator. 
    // The '+1' can be removed since it's not _technically_ needed since 
    // it's a string_view -- but this can be useful for C interop.
    static constexpr std::array<char,N+1> value = make_indent_string<N>();
};

这个 make_indent_string<N>() 现在只是一个简单的包装器,它创建一个 std::array 并用制表符填充它:

// Thanks to @Barry's suggestion to use 'fill' rather than
// index_sequence
template <std::size_t N>
consteval auto make_indent_string() -> std::array<char,N+1>
{
    auto result = std::array<char,N+1>{};
    result.fill('\t');
    result.back() = '[=12=]';
    return result;
}

然后 indent<N> 就变成了 holder 的包装纸:

template <std::size_t N>
consteval auto indent() -> std::string_view
{ 
    const auto& str = indent_string_holder<N>::value;

    // -1 on the size if we added the null-terminator.
    // This could also be just string_view{str.data()} with the
    // terminator
    return std::string_view{str.data(), str.size() - 1u}; 
}

我们可以做一个简单的测试,看看这是否在编译时有效,它应该:

static_assert(indent<5>() == "\t\t\t\t\t");

Live Example

如果您检查程序集,您还会看到 indent<5>() 根据需要生成正确的编译时字符串:

indent_string_holder<5ul>::value:
        .asciz  "\t\t\t\t\t"

虽然这行得通,但根据 FileWrite(或任何基础 class 是什么——假设这是 ostream) 而不是 returning a string_view。除非您对这些流进行缓冲写入,否则与刷新数据的成本相比,写入几个单个字符的成本应该是最小的——这应该可以忽略不计。

如果这是可以接受的,那么实际上会容易得多,因为您现在可以将其编写为递归函数,将 \t 传递给您的流对象,然后调用 indent<N-1>(...),例如:

template <std::size_t N>
auto indent(FileWrite& f) -> FileWrite&
{
    if constexpr (N > 0) {
        f << '\t'; // Output a single tab
        return indent<N-1>(f);
    }
    return f;
}

这改变了现在的用法:

FileWrite f(path);

f<< "<root>\n"sv;
indent<1>(f) << "<nested1>\n"sv;
indent<2>(f) << "<nested2>\n"sv;
indent<3>(f) << "<nested3>\n"sv;
indent<4>(f) << "<nested4>\n"sv;

但与在编译时生成字符串相比,IMO 的实现更容易理解。

实际上,在这一点上,这样写可能更简洁:

auto indent(FileWrite& f, std::size_t n) -> FileWrite&
{
    for (auto i = 0u; i < n; ++i) { f << '\t'; }
    return f;
}

这可能是大多数人希望阅读的内容;尽管它确实以最小的循环成本出现(前提是优化器不展开它)。