c++ - 创建一个重复字符 n 次的编译时间字符串
问题描述
我正在使用这样的函数将数据导出到 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),因为存储在末尾超出范围功能。所以你需要解决这个问题。
最简单的方法是使用 a template
of astruct
来保存一个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>()
只是一个简单的包装器,它创建 astd::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>
就变成了持有人的包装:
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");
如果您检查程序集,您还将看到它indent<5>()
根据需要生成正确的编译时字符串:
indent_string_holder<5ul>::value:
.asciz "\t\t\t\t\t"
indent<N>()
虽然这可行,但实际上你用FileWrite
(或任何基类——假设这是ostream
)而不是返回 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;
}
这可能是大多数人希望阅读的内容;尽管它确实以最小的循环成本来实现(前提是优化器没有展开它)。
推荐阅读
- html - 将一排 div 居中在容器中
- firebase - 如何在flutter firestore中获取用户ID
- java - 后台服务被暂停/终止
- azure-functions - 提高 Azure Functions v2 中的并行度
- php - 如何将具有相同值的行从 mySQL 分组到 php 中?
- python - 在继承中查询特定的子类(子类)将呈现除子类属性之外的基类(父类)属性
- docker - 防止 Chrome-Headless 强制执行 ssl
- javascript - 在向前看之前只捕获最接近的匹配
- teradata - 通过在 Teradata SQL 助手中运行查询将结果导出到 Excel 工作表时出现回车问题
- testing - 是否有更好的策略来减少测试用例规范中的冗余?