C++ 漂亮打印 std::tuple
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6245735/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Pretty-print std::tuple
提问by Kerrek SB
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.
这是我之前关于漂亮打印 STL 容器的问题的后续,为此我们设法开发了一个非常优雅且完全通用的解决方案。
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::tuple<Args...>
使用可变参数模板(因此严格来说是 C++11)包含漂亮的打印。对于std::pair<S,T>
,我简单地说
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:
我尝试了各种模板参数堆栈解包,传递索引并使用 SFINAE 来发现我何时到达最后一个元素,但没有成功。我不会用我的破密码给你带来负担;希望问题描述足够直截了当。基本上,我想要以下行为:
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!
包含与上一个问题相同级别的通用性(char/wchar_t,对分隔符)的奖励积分!
采纳答案by Xeo
Yay, indices~
耶,指数~
namespace aux{
template<std::size_t...> struct seq{};
template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch,Tr>& os, Tuple const& t, seq<Is...>){
using swallow = int[];
(void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
}
} // aux::
template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
-> std::basic_ostream<Ch, Tr>&
{
os << "(";
aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());
return os << ")";
}
For the delimiter stuff, just add these partial specializations:
对于分隔符,只需添加这些部分专业化:
// Delimiters for tuple
template<class... Args>
struct delimiters<std::tuple<Args...>, char> {
static const delimiters_values<char> values;
};
template<class... Args>
const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = { "(", ", ", ")" };
template<class... Args>
struct delimiters<std::tuple<Args...>, wchar_t> {
static const delimiters_values<wchar_t> values;
};
template<class... Args>
const delimiters_values<wchar_t> delimiters<std::tuple<Args...>, wchar_t>::values = { L"(", L", ", L")" };
and change the operator<<
and print_tuple
accordingly:
并相应地更改operator<<
和print_tuple
:
template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
-> std::basic_ostream<Ch, Tr>&
{
typedef std::tuple<Args...> tuple_t;
if(delimiters<tuple_t, Ch>::values.prefix != 0)
os << delimiters<tuple_t,char>::values.prefix;
print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());
if(delimiters<tuple_t, Ch>::values.postfix != 0)
os << delimiters<tuple_t,char>::values.postfix;
return os;
}
And
和
template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch, Tr>& os, Tuple const& t, seq<Is...>){
using swallow = int[];
char const* delim = delimiters<Tuple, Ch>::values.delimiter;
if(!delim) delim = "";
(void)swallow{0, (void(os << (Is == 0? "" : delim) << std::get<Is>(t)), 0)...};
}
回答by Tony Olsson
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).
我在 C++11(gcc 4.7)中运行良好。我确信有一些陷阱我没有考虑过,但我认为代码很容易阅读,而且并不复杂。唯一可能奇怪的是“守卫”结构 tuple_printer,它确保我们在到达最后一个元素时终止。另一个奇怪的事情可能是 sizeof...(Types) 返回 Types 类型包中类型的数量。它用于确定最后一个元素的索引(大小...(类型)- 1)。
template<typename Type, unsigned N, unsigned Last>
struct tuple_printer {
static void print(std::ostream& out, const Type& value) {
out << std::get<N>(value) << ", ";
tuple_printer<Type, N + 1, Last>::print(out, value);
}
};
template<typename Type, unsigned N>
struct tuple_printer<Type, N, N> {
static void print(std::ostream& out, const Type& value) {
out << std::get<N>(value);
}
};
template<typename... Types>
std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) {
out << "(";
tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value);
out << ")";
return out;
}
回答by AndyG
In C++17 we can accomplish this with a little less code by taking advantage of Fold expressions, particularly a unary left fold:
在 C++17 中,我们可以通过利用折叠表达式,特别是一元左折叠,用更少的代码来实现这一点:
template<class TupType, size_t... I>
void print(const TupType& _tup, std::index_sequence<I...>)
{
std::cout << "(";
(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
std::cout << ")\n";
}
template<class... T>
void print (const std::tuple<T...>& _tup)
{
print(_tup, std::make_index_sequence<sizeof...(T)>());
}
Live Demooutputs:
现场演示输出:
(5, Hello, -0.1)
(5,你好,-0.1)
given
给予
auto a = std::make_tuple(5, "Hello", -0.1);
print(a);
Explanation
解释
Our unary left fold is of the form
我们的一元左折叠的形式是
... op pack
where op
in our scenario is the comma operator, and pack
is the expression containing our tuple in an unexpanded context like:
其中op
在我们的方案中是逗号运算符,并且pack
是包含在象未扩展上下文我们的元组中的表达:
(..., (std::cout << std::get<I>(myTuple))
So if I have a tuple like so:
所以如果我有一个像这样的元组:
auto myTuple = std::make_tuple(5, "Hello", -0.1);
And a std::integer_sequence
whose values are specified by a non-type template (see above code)
并且 astd::integer_sequence
其值由非类型模板指定(参见上面的代码)
size_t... I
Then the expression
那么表达式
(..., (std::cout << std::get<I>(myTuple))
Gets expanded into
扩展为
((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));
Which will print
哪个会打印
5Hello-0.1
5Hello-0.1
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 index I
is not the first, hence the (I == 0? "" : ", ")
portion*:
为了实现这一点,如果当前索引不是第一个,我们修改pack
折叠表达式的部分以打印,因此部分*:" ,"
I
(I == 0? "" : ", ")
(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
And now we'll get
现在我们会得到
5, Hello, -0.1
5、你好,-0.1
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 afterinstead of beforeby testing against std::tuple_size<TupType>::value - 1
, but that was too long, so I tested instead against sizeof...(I) - 1
, but in the end I copied Xeoand we ended up with what I've got.
*注意:您可以通过多种方式进行逗号分隔,而不是我最终采用的方式。我最初通过对 进行测试,有条件地在 after而不是before 中添加了逗号std::tuple_size<TupType>::value - 1
,但那太长了,所以我改为对 进行了测试sizeof...(I) - 1
,但最后我复制了Xeo,我们最终得到了我所得到的。
回答by AndyG
I'm surprised the implementation on cppreferencehas 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.
我很惊讶cppreference的实现还没有在这里发布,所以我会为后代做这件事。它隐藏在文档中,std::tuple_cat
因此不容易找到。它像这里的其他一些解决方案一样使用保护结构,但我认为他们的最终更简单,更容易遵循。
#include <iostream>
#include <tuple>
#include <string>
// helper function to print a tuple of any size
template<class Tuple, std::size_t N>
struct TuplePrinter {
static void print(const Tuple& t)
{
TuplePrinter<Tuple, N-1>::print(t);
std::cout << ", " << std::get<N-1>(t);
}
};
template<class Tuple>
struct TuplePrinter<Tuple, 1> {
static void print(const Tuple& t)
{
std::cout << std::get<0>(t);
}
};
template<class... Args>
void print(const std::tuple<Args...>& t)
{
std::cout << "(";
TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
std::cout << ")\n";
}
// end helper function
And a test:
还有一个测试:
int main()
{
std::tuple<int, std::string, float> t1(10, "Test", 3.14);
int n = 7;
auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n));
n = 10;
print(t2);
}
Output:
输出:
(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)
(10, 测试, 3.14, Foo, bar, 10, 测试, 3.14, 10)
回答by user5673656
Based on AndyG code, for C++17
基于 AndyG 代码,适用于 C++17
#include <iostream>
#include <tuple>
template<class TupType, size_t... I>
std::ostream& tuple_print(std::ostream& os,
const TupType& _tup, std::index_sequence<I...>)
{
os << "(";
(..., (os << (I == 0 ? "" : ", ") << std::get<I>(_tup)));
os << ")";
return os;
}
template<class... T>
std::ostream& operator<< (std::ostream& os, const std::tuple<T...>& _tup)
{
return tuple_print(os, _tup, std::make_index_sequence<sizeof...(T)>());
}
int main()
{
std::cout << "deep tuple: " << std::make_tuple("Hello",
0.1, std::make_tuple(1,2,3,"four",5.5), 'Z')
<< std::endl;
return 0;
}
with output:
带输出:
deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
回答by DarioP
Leveraging on std::apply
(C++17) we can drop the std::index_sequence
and define a single function:
利用std::apply
(C++17) 我们可以删除std::index_sequence
并定义单个函数:
#include <tuple>
#include <iostream>
template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
std::apply([&os](auto&&... args) {((os << args << " "), ...);}, t);
return os;
}
Or, slightly embellished with the help of a stringstream:
或者,在字符串流的帮助下稍微修饰一下:
#include <tuple>
#include <iostream>
#include <sstream>
template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
std::basic_stringstream<Ch, Tr> ss;
ss << "[ ";
std::apply([&ss](auto&&... args) {((ss << args << ", "), ...);}, t);
ss.seekp(-2, ss.cur);
ss << " ]";
return os << ss.str();
}
回答by CW Holeman II
Based upon example on The C++ Programming Language By Bjarne Stroustrup, page 817:
基于Bjarne Stroustrup 撰写的 The C++ Programming Language,第 817 页的示例:
#include <tuple>
#include <iostream>
#include <string>
#include <type_traits>
template<size_t N>
struct print_tuple{
template<typename... T>static typename std::enable_if<(N<sizeof...(T))>::type
print(std::ostream& os, const std::tuple<T...>& t) {
char quote = (std::is_convertible<decltype(std::get<N>(t)), std::string>::value) ? '"' : 0;
os << ", " << quote << std::get<N>(t) << quote;
print_tuple<N+1>::print(os,t);
}
template<typename... T>static typename std::enable_if<!(N<sizeof...(T))>::type
print(std::ostream&, const std::tuple<T...>&) {
}
};
std::ostream& operator<< (std::ostream& os, const std::tuple<>&) {
return os << "()";
}
template<typename T0, typename ...T> std::ostream&
operator<<(std::ostream& os, const std::tuple<T0, T...>& t){
char quote = (std::is_convertible<T0, std::string>::value) ? '"' : 0;
os << '(' << quote << std::get<0>(t) << quote;
print_tuple<1>::print(os,t);
return os << ')';
}
int main(){
std::tuple<> a;
auto b = std::make_tuple("One meatball");
std::tuple<int,double,std::string> c(1,1.2,"Tail!");
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
}
Output:
输出:
()
("One meatball")
(1, 1.2, "Tail!")
回答by Gabriel
Another one, similar to @Tony Olsson's, including a specialization for the empty tuple, as suggested by @Kerrek SB.
另一个类似于@Tony Olsson 的,包括空元组的专门化,如@Kerrek SB 所建议的。
#include <tuple>
#include <iostream>
template<class Ch, class Tr, size_t I, typename... TS>
struct tuple_printer
{
static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
{
tuple_printer<Ch, Tr, I-1, TS...>::print(out, t);
if (I < sizeof...(TS))
out << ",";
out << std::get<I>(t);
}
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, 0, TS...>
{
static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
{
out << std::get<0>(t);
}
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, -1, TS...>
{
static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
{}
};
template<class Ch, class Tr, typename... TS>
std::ostream & operator<<(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
{
out << "(";
tuple_printer<Ch, Tr, sizeof...(TS) - 1, TS...>::print(out, t);
return out << ")";
}
回答by user2445507
I like DarioP's answer, but stringstream uses heap. This can be avoided:
我喜欢 DarioP 的答案,但 stringstream 使用堆。这可以避免:
template <class... Args>
std::ostream& operator<<(std::ostream& os, std::tuple<Args...> const& t) {
os << "(";
bool first = true;
std::apply([&os, &first](auto&&... args) {
auto print = [&] (auto&& val) {
if (!first)
os << ",";
(os << " " << val);
first = false;
};
(print(args), ...);
}, t);
os << " )";
return os;
}