漂亮的打印 std::tuple

发布于 2024-11-14 01:59:23 字数 812 浏览 3 评论 0原文

这是我之前关于 漂亮打印 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

战皆罪 2024-11-21 01:59:23

耶,索引~

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

Ideone 上的实时示例。


对于分隔符内容,只需添加这些部分专业化:

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

并更改 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;
}

并且

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)...};
}

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.


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:

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)...};
}
↙厌世 2024-11-21 01:59:23

在 C++17 中,我们可以利用 折叠表达式< /a>,特别是一元左折叠:

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 Demo 输出:

(5,你好,-0.1)

给出的

auto a = std::make_tuple(5, "Hello", -0.1);
print(a);

解释

我们的一元左折叠的形式是

... op pack

,其中 op 在我们的场景中是逗号运算符,而 pack 是包含我们的元组的表达式在未扩展的上下文中,例如:

(..., (std::cout << std::get<I>(myTuple))

因此,如果我有一个像这样的元组:

auto myTuple = std::make_tuple(5, "Hello", -0.1);

以及 std::integer_sequence 其值由非类型模板指定(参见上面的代码)

size_t... I

然后表达式

(..., (std::cout << std::get<I>(myTuple))

扩展为

((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));

Which will print

5Hello-0.1

这很糟糕,所以我们需要做一些更多的技巧来添加一个要首先打印的逗号分隔符,除非它是第一个元素。

为此,我们修改折叠表达式的 pack 部分,以在当前索引 I 不是第一个索引时打印 " ," ,因此(I == 0? "" : ", ") 部分*

(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));

现在我们会得到

5,你好,-0.1

看起来更好(注意:我想要与这个答案类似的输出)

*注意:除了我最终采用的方法之外,您还可以通过多种方式来进行逗号分隔。我最初通过测试 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:

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 Demo outputs:

(5, Hello, -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:

(..., (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)

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

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

(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));

And now we'll get

5, Hello, -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 after instead of before by 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 Xeo and we ended up with what I've got.

人心善变 2024-11-21 01:59:23

我在 C++11 (gcc 4.7) 中工作得很好。我确信有一些我没有考虑到的陷阱,但我认为代码很容易阅读而且并不复杂。唯一可能奇怪的是“守卫”struct 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;
}

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).

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;
}
懒的傷心 2024-11-21 01:59:23

我很惊讶 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

和一个测试:

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);
}

输出:

(10,测试,3.14,Foo,bar,10,测试,3.14,10)

现场演示

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.

#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)

Live Demo

路弥 2024-11-21 01:59:23

利用 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;
}

或者,在字符串流的帮助下稍微修饰一下:

#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();
}

Leveraging on std::apply (C++17) we can drop the std::index_sequence and define a single function:

#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();
}
别挽留 2024-11-21 01:59:23

基于 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;
}

输出为:

deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)

Based on AndyG code, for 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)
我做我的改变 2024-11-21 01:59:23

基于 Bjarne Stroustrup 所著的 C++ 编程语言,第 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;
    }

输出:

()
("One meatball")
(1, 1.2, "Tail!")

Based upon example on The C++ Programming Language By Bjarne Stroustrup, page 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!")
ぃ双果 2024-11-21 01:59:23

我喜欢 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;
}

I like DarioP's answer, but stringstream uses heap. This can be avoided:

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;
}
夏末 2024-11-21 01:59:23

从 C++23 开始,您可以使用 std::formatstd::print (或 std::println) 来执行此操作。

#include <print>

int main() {
    std::print("{}", std::make_tuple(1, 5.5, "text"));
}
(1, 5.5, "text")

如果您需要 std::string 形式的结果,请使用 std::format。请注意,格式化元组需要 C++23 支持,特别是 P2286R8 的实现(检查 C++23 库功能)。

#include <format>

int main() {
    std::string text = std::format("{}", std::make_tuple(1, 5.5, "text"));
}

如果您坚持使用较旧的标准,您可以使用 fmt 库到打印元组

#include <fmt/ranges.h>

int main() {
    // direct print
    fmt::print("{}", std::make_tuple(1, 5.5, "text"));

    // create std::string object
    std::string str = fmt::format("{}", std::make_tuple(1, 5.5, "text"));
}

Starting with C++23 you can use std::format or std::print (or std::println) to do this.

#include <print>

int main() {
    std::print("{}", std::make_tuple(1, 5.5, "text"));
}
(1, 5.5, "text")

If you need the result as std::string use std::format. Note that formatting tuples requires C++23 support, specifically the implementation of P2286R8 (check C++23 library features).

#include <format>

int main() {
    std::string text = std::format("{}", std::make_tuple(1, 5.5, "text"));
}

If you are stuck to an older standard, you can use the fmt library to print tuples.

#include <fmt/ranges.h>

int main() {
    // direct print
    fmt::print("{}", std::make_tuple(1, 5.5, "text"));

    // create std::string object
    std::string str = fmt::format("{}", std::make_tuple(1, 5.5, "text"));
}
血之狂魔 2024-11-21 01:59:23

另一种与 @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 << ")";
}

Another one, similar to @Tony Olsson's, including a specialization for the empty tuple, as suggested by @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 << ")";
}
死开点丶别碍眼 2024-11-21 01:59:23

这是我最近为打印元组编写的一些代码。

#include <iostream>
#include <tuple>

using namespace std;

template<typename... Ts>
ostream& operator<<(ostream& output, const tuple<Ts...> t) {
    output << '(';
    apply([&](auto&&... args) {
        ((cout << args << ", "), ...);
    }, t);
    output << "\b\b";
    output << ')';
    return output;
}

使用您的示例案例:

auto a = std::make_tuple(5, "Hello", -0.1); 
cout << a << '\n'; // (5, Hello, -0.1)

Here's some code I recently made for printing a tuple.

#include <iostream>
#include <tuple>

using namespace std;

template<typename... Ts>
ostream& operator<<(ostream& output, const tuple<Ts...> t) {
    output << '(';
    apply([&](auto&&... args) {
        ((cout << args << ", "), ...);
    }, t);
    output << "\b\b";
    output << ')';
    return output;
}

Using your example case:

auto a = std::make_tuple(5, "Hello", -0.1); 
cout << a << '\n'; // (5, Hello, -0.1)
江湖正好 2024-11-21 01:59:23

我看到使用 std::index_sequence 和 C++17 的答案,但是,这不是我个人会走的路。我宁愿采用递归和 constexpr if :

#include <tuple>
#include <iostream>

template<std::size_t I, class... Ts>
void doPrintTuple(const std::tuple<Ts...>& tuples) {
    if constexpr (I == sizeof...(Ts)) {
        std::cout << ')';
    }
    else {
        std::cout << std::get<I>(tuples);
        if constexpr (I + 1 != sizeof...(Ts)) {
            std::cout << ", ";
        }
        doPrintTuple<I + 1>(tuples);
    }
}

template<class... Ts>
void printTuple(const std::tuple<Ts...>& tuples) {
    std::cout << '(';
    doPrintTuple<0>(tuples);
}

int main() {
    auto tup = std::make_tuple(1, "hello", 4.5);
    printTuple(tup);
}

输出:

(1, hello, 4.5)

请注意,这不适用于嵌套元组。

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 and constexpr if:

#include <tuple>
#include <iostream>

template<std::size_t I, class... Ts>
void doPrintTuple(const std::tuple<Ts...>& tuples) {
    if constexpr (I == sizeof...(Ts)) {
        std::cout << ')';
    }
    else {
        std::cout << std::get<I>(tuples);
        if constexpr (I + 1 != sizeof...(Ts)) {
            std::cout << ", ";
        }
        doPrintTuple<I + 1>(tuples);
    }
}

template<class... Ts>
void printTuple(const std::tuple<Ts...>& tuples) {
    std::cout << '(';
    doPrintTuple<0>(tuples);
}

int main() {
    auto tup = std::make_tuple(1, "hello", 4.5);
    printTuple(tup);
}

Output:

(1, hello, 4.5)

Note that this does not work for nested tuples.

往日情怀 2024-11-21 01:59:23

对于之前使用折叠表达式的答案,我不喜欢的一件事是它们使用索引序列或标志来跟踪第一个元素,这消除了干净折叠表达式的大部分好处。

这是一个不需要索引但达到类似结果的示例。 (不像其他一些复杂,但可以添加更多。)

该技术是使用折叠已经为您提供的内容:一个元素的特殊情况。即,一个元素折叠仅扩展为 elem[0],然后 2 个元素是 elem[0] + elem[1],其中 +是一些操作。我们想要的是一个元素只将该元素写入流中,对于更多元素,执行相同的操作,但通过附加写入“,”来连接每个元素。因此,将其映射到 C++ 折叠上,我们希望每个元素都是将某个对象写入流的操作。我们希望我们的 + 操作是用“,”写入来散布两个写入。因此,首先将我们的元组序列转换为写入操作序列,我将其称为 CommaJoiner,然后为该操作添加一个 operator+ 以按照我们想要的方式连接两个操作,在两者之间添加一个“,”:

#include <tuple>
#include <iostream>

template <typename T>
struct CommaJoiner
{
    T thunk;
    explicit CommaJoiner(const T& t) : thunk(t) {}

    template <typename S>
    auto operator+(CommaJoiner<S> const& b) const
    {
        auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) {
            a(os);
            os << ", ";
            b(os);
        };
        return CommaJoiner<decltype(joinedThunk)>{joinedThunk};
    }

    void operator()(std::ostream& os) const
    {
        thunk(os);
    }

};

template <typename ...Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup)
{
    std::apply([&](auto ...ts) {
        return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os);

    return os;
}

int main() {
    auto tup = std::make_tuple(1, 2.0, "Hello");
    std::cout << tup << std::endl;
}

粗略地浏览一下 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 is elem[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 an operator+ to join two actions in the way we want, adding a ", " in between:

#include <tuple>
#include <iostream>

template <typename T>
struct CommaJoiner
{
    T thunk;
    explicit CommaJoiner(const T& t) : thunk(t) {}

    template <typename S>
    auto operator+(CommaJoiner<S> const& b) const
    {
        auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) {
            a(os);
            os << ", ";
            b(os);
        };
        return CommaJoiner<decltype(joinedThunk)>{joinedThunk};
    }

    void operator()(std::ostream& os) const
    {
        thunk(os);
    }

};

template <typename ...Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup)
{
    std::apply([&](auto ...ts) {
        return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os);

    return os;
}

int main() {
    auto tup = std::make_tuple(1, 2.0, "Hello");
    std::cout << tup << std::endl;
}

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文