gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 14:54:45 26 4
gpt4 key购买 nike

我正在使用这样的函数将数据导出到 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::string 添加重载, std::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() 的提示? ?我不确定我的想法是否返回 std::string_view指向在编译时分配的静态常量缓冲区是最合适的,我愿意接受其他建议。

最佳答案

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

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

这会将您的签名更改为:

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

问题的第二部分是您希望它返回 std::string_view .这里的并发症是constexpr上下文不允许static变量——因此您在上下文中创建的任何内容都将自动存储持续时间。从技术上讲,您不能只是简单地在函数中创建一个数组并返回一个 string_view。它的——因为这会导致一个悬空指针(以及 UB),因为在函数结束时存储超出范围。所以你需要解决这个问题。

最简单的方法是使用 templatestruct持有 static数组(在本例中为 std::array,因此我们可以从函数中返回它):

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() = '\0';
return result;
}

然后 indent<N>只是成为支架周围的 wrapper :

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"

虽然这行得通,但实际上编写 indent<N>() 可能会简单得多在 FileWrite 方面(或者无论基类是什么 - 假设这是 ostream )而不是返回 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;
}

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

关于c++ - 创建一个编译时字符串重复一个字符 n 次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68286513/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com