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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 19:45:34  来源:igfitidea点击:

Pretty-print std::tuple

c++c++11tuplesvariadic-templates

提问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 << ")";
}

Live example on Ideone.

Ideone 上的现场示例。



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_tupleaccordingly:

并相应地更改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 opin our scenario is the comma operator, and packis 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_sequencewhose 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 packportion of the fold expression to print " ,"if the current index Iis 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_catso 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)

Live Demo

现场演示

回答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_sequenceand 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;
}