漂亮的打印 std::tuple
这是我之前关于 漂亮打印 STL 容器 问题的后续问题,对于我们成功地开发了一个非常优雅且完全通用的解决方案。
在下一步中,我想使用可变参数模板包含 std::tuple
的漂亮打印(因此这严格来说是 C++11)。对于 std::pair
,我只是说
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
return o << "(" << p.first << ", " << p.second << ")";
}
打印元组的类似结构是什么?
我尝试过各种模板参数堆栈解包、传递索引并使用 SFINAE 来发现我何时到达最后一个元素,但没有成功。我不会用我损坏的代码给你带来负担;希望问题描述足够简单。本质上,我想要以下行为:
auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)
包含与上一个问题相同级别的通用性(char/wchar_t,对分隔符)的加分!
This is a follow-up to my previous question on pretty-printing STL containers, for which we managed to develop a very elegant and fully general solution.
In this next step, I would like to include pretty-printing for std::tuple<Args...>
, using variadic templates (so this is strictly C++11). For std::pair<S,T>
, I simply say
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
return o << "(" << p.first << ", " << p.second << ")";
}
What is the analogous construction for printing a tuple?
I've tried various bits of template argument stack unpacking, passing indices around and using SFINAE to discover when I'm at the last element, but with no success. I shan't burden you with my broken code; the problem description is hopefully straight-forward enough. Essentially, I'd like the following behaviour:
auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)
Bonus points for including the same level of generality (char/wchar_t, pair delimiters) as the the previous question!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
耶,索引~
Ideone 上的实时示例。
对于分隔符内容,只需添加这些部分专业化:
并更改
operator<<
和print_tuple
因此:并且
Yay, indices~
Live example on Ideone.
For the delimiter stuff, just add these partial specializations:
and change the
operator<<
andprint_tuple
accordingly:And
在 C++17 中,我们可以利用 折叠表达式< /a>,特别是一元左折叠:
Live Demo 输出:
给出的
解释
我们的一元左折叠的形式是
,其中
op
在我们的场景中是逗号运算符,而pack
是包含我们的元组的表达式在未扩展的上下文中,例如:因此,如果我有一个像这样的元组:
以及
std::integer_sequence
其值由非类型模板指定(参见上面的代码)然后表达式
扩展为
Which will print
这很糟糕,所以我们需要做一些更多的技巧来添加一个要首先打印的逗号分隔符,除非它是第一个元素。
为此,我们修改折叠表达式的
pack
部分,以在当前索引I
不是第一个索引时打印" ,"
,因此(I == 0? "" : ", ")
部分*:现在我们会得到
看起来更好(注意:我想要与这个答案类似的输出)
*注意:除了我最终采用的方法之外,您还可以通过多种方式来进行逗号分隔。我最初通过测试
std::tuple_size::value - 1
有条件地在之后而不是之前添加逗号,但这也太长,所以我测试了sizeof...(I) - 1
,但最终我复制了 Xeo,我们最终得到了我所得到的。In C++17 we can accomplish this with a little less code by taking advantage of Fold expressions, particularly a unary left fold:
Live Demo outputs:
given
Explanation
Our unary left fold is of the form
where
op
in our scenario is the comma operator, andpack
is the expression containing our tuple in an unexpanded context like:So if I have a tuple like so:
And a
std::integer_sequence
whose values are specified by a non-type template (see above code)Then the expression
Gets expanded into
Which will print
Which is gross, so we need to do some more trickery to add a comma separator to be printed first unless it's the first element.
To accomplish that, we modify the
pack
portion of the fold expression to print" ,"
if the current indexI
is not the first, hence the(I == 0? "" : ", ")
portion*:And now we'll get
Which looks nicer (Note: I wanted similar output as this answer)
*Note: You could do the comma separation in a variety of ways than what I ended up with. I initially added commas conditionally after instead of before by testing against
std::tuple_size<TupType>::value - 1
, but that was too long, so I tested instead againstsizeof...(I) - 1
, but in the end I copied Xeo and we ended up with what I've got.我在 C++11 (gcc 4.7) 中工作得很好。我确信有一些我没有考虑到的陷阱,但我认为代码很容易阅读而且并不复杂。唯一可能奇怪的是“守卫”struct tuple_printer,它确保我们在到达最后一个元素时终止。另一个奇怪的事情可能是 sizeof...(Types),它返回 Types 类型包中的类型数量。它用于确定最后一个元素的索引(大小...(类型)- 1)。
I got this working fine in C++11 (gcc 4.7). There are I am sure some pitfalls I have not considered but I think the code is easy to read and and not complicated. The only thing that may be strange is the "guard" struct tuple_printer that ensure that we terminate when the last element is reached. The other strange thing may be sizeof...(Types) that return the number of types in Types type pack. It is used to determine the index of the last element (size...(Types) - 1).
我很惊讶 cppreference 上的实现尚未发布在这里,所以我会为子孙后代做这件事。它隐藏在
std::tuple_cat
的文档中,因此不容易找到。它使用像这里的其他一些解决方案一样的保护结构,但我认为他们的最终更简单且更易于遵循。和一个测试:
输出:
现场演示
I'm surprised the implementation on cppreference has not already been posted here, so I'll do it for posterity. It's hidden in the doc for
std::tuple_cat
so it's not easy to find. It uses a guard struct like some of the other solutions here, but I think theirs is ultimately simpler and easier-to-follow.And a test:
Output:
Live Demo
利用
std::apply
(C++17),我们可以删除std::index_sequence
并定义一个函数:或者,在字符串流的帮助下稍微修饰一下:
Leveraging on
std::apply
(C++17) we can drop thestd::index_sequence
and define a single function:Or, slightly embellished with the help of a stringstream:
基于 AndyG 代码,对于 C++17,
输出为:
Based on AndyG code, for C++17
with output:
基于 Bjarne Stroustrup 所著的 C++ 编程语言,第 817 页中的示例:
输出:
Based upon example on The C++ Programming Language By Bjarne Stroustrup, page 817:
Output:
我喜欢 DarioP 的答案,但 stringstream 使用堆。这是可以避免的:
I like DarioP's answer, but stringstream uses heap. This can be avoided:
从 C++23 开始,您可以使用
std::format
或std::print
(或std::println
) 来执行此操作。如果您需要
std::string
形式的结果,请使用std::format
。请注意,格式化元组需要 C++23 支持,特别是 P2286R8 的实现(检查 C++23 库功能)。如果您坚持使用较旧的标准,您可以使用 fmt 库到打印元组。
Starting with C++23 you can use
std::format
orstd::print
(orstd::println
) to do this.If you need the result as
std::string
usestd::format
. Note that formatting tuples requires C++23 support, specifically the implementation of P2286R8 (check C++23 library features).If you are stuck to an older standard, you can use the fmt library to print tuples.
另一种与 @Tony Olsson 类似,包括空元组的专门化,如 @Kerrek SB 所建议。
Another one, similar to @Tony Olsson's, including a specialization for the empty tuple, as suggested by @Kerrek SB.
这是我最近为打印元组编写的一些代码。
使用您的示例案例:
Here's some code I recently made for printing a tuple.
Using your example case:
我看到使用
std::index_sequence
和 C++17 的答案,但是,这不是我个人会走的路。我宁愿采用递归和 constexpr if :输出:
请注意,这不适用于嵌套元组。
I see answers using
std::index_sequence
with C++17, however, that's not the road I'd go personally. I'd rather go for recursion andconstexpr if
:Output:
Note that this does not work for nested tuples.
对于之前使用折叠表达式的答案,我不喜欢的一件事是它们使用索引序列或标志来跟踪第一个元素,这消除了干净折叠表达式的大部分好处。
这是一个不需要索引但达到类似结果的示例。 (不像其他一些复杂,但可以添加更多。)
该技术是使用折叠已经为您提供的内容:一个元素的特殊情况。即,一个元素折叠仅扩展为
elem[0]
,然后 2 个元素是elem[0] + elem[1]
,其中+
是一些操作。我们想要的是一个元素只将该元素写入流中,对于更多元素,执行相同的操作,但通过附加写入“,”来连接每个元素。因此,将其映射到 C++ 折叠上,我们希望每个元素都是将某个对象写入流的操作。我们希望我们的+
操作是用“,”写入来散布两个写入。因此,首先将我们的元组序列转换为写入操作序列,我将其称为CommaJoiner
,然后为该操作添加一个operator+
以按照我们想要的方式连接两个操作,在两者之间添加一个“,”:粗略地浏览一下 godbolt 表明它编译得也很好,所有的 thunk 调用都被扁平化了。
然而,这将需要第二次重载来处理空元组。
One thing I dislike about the previous answers that use fold expressions is that they use index sequences or flags to keep track of the first element, which removes much of the benefit of nice clean fold expressions.
Here is an example that does not need indexing, but achieves a similar result. (Not as sophisticated as some of the others, but more could be added.)
The technique is to use what the fold already gives you: a special case for one element. I.e., one element fold just expands to
elem[0]
, then 2 elements iselem[0] + elem[1]
, where+
is some operation. What we want is for one element to write just that element to the stream, and for more elements, do the same, but join each one with an additional write of ", ". So mapping this on to the c++ fold, we want each element to be the action of writing some object to the stream. We want our+
operation to be to intersperse two writes with a ", " write. So first transform our tuple sequence into a sequence of write actions,CommaJoiner
I have called it, then for that action add anoperator+
to join two actions in the way we want, adding a ", " in between:A cursory glance at godbolt suggests that this compiles quite well too, all the thunks calls being flattened.
This will need a second overload to deal with an empty tuple however.