重载运算符<< for std::tuple - possible simplications?

问题描述 投票:0回答:6

我使用 对 SO 问题“遍历元组” 的回答来编写重载方法

<<
。这个方法已经过测试,似乎可以在 Debian squeeze 上与
g++ 4.7
一起正常工作。

但是这种方法有点迂回,因为看起来

<<
不能显式实例化(我找到了一篇关于它的帖子 这里)。因此,人们被迫定义一个字符串方法然后调用它。 vector我有类似的方法,比较直接。有没有人对如何消除使用相同方法或其他方式创建字符串方法的额外步骤提出建议?提前致谢。

#include <tuple>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>

using std::ostream;
using std::cout;
using std::endl;
using std::vector;
using std::string;

// Print vector<T>.
template<typename T> ostream& operator <<(ostream& out, const vector<T> & vec)
{
  unsigned int i;
  out << "[";
  for(i=0; i<vec.size(); i++)
    {
      out << vec[i];
      if(i < vec.size() - 1)
    out << ", ";
    }
  out << "]";
  return out;
}

////////////////////////////////////////////////////////////////

// Print tuple.
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), string>::type
stringval(const std::tuple<Tp...> & t)
{
  std::stringstream buffer;
  buffer << "]";
  return buffer.str();
}

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), string>::type
stringval(const std::tuple<Tp...> & t)
{
  std::stringstream buffer;
  size_t len = sizeof...(Tp);
  if(I==0)
      buffer << "[";
  buffer << std::get<I>(t);
  if(I < len - 1)
    buffer << ", ";
  buffer << stringval<I + 1, Tp...>(t);
  return buffer.str();
}

template<typename... Tp> ostream& operator <<(ostream& out, const std::tuple<Tp...> & t)
{
  out << stringval(t);
  return out;
}

int
main()
{
  typedef std::tuple<int, float, double> T;
  std::tuple<int, float, double> t = std::make_tuple(2, 3.14159F, 2345.678);
  cout << t << endl;
}

编译时,这给出了

[2, 3.14159, 2345.68]
c++ templates c++11 tuples iterable-unpacking
6个回答
5
投票

您可以将

std::ostream&
传递给该
stringval
函数并使用
out <<
而不是
buffer <<
.

演示

#include <tuple>
#include <iostream>
#include <type_traits>

template <size_t n, typename... T>
typename std::enable_if<(n >= sizeof...(T))>::type
    print_tuple(std::ostream&, const std::tuple<T...>&)
{}

template <size_t n, typename... T>
typename std::enable_if<(n < sizeof...(T))>::type
    print_tuple(std::ostream& os, const std::tuple<T...>& tup)
{
    if (n != 0)
        os << ", ";
    os << std::get<n>(tup);
    print_tuple<n+1>(os, tup);
}

template <typename... T>
std::ostream& operator<<(std::ostream& os, const std::tuple<T...>& tup)
{
    os << "[";
    print_tuple<0>(os, tup);
    return os << "]";
}

3
投票

基于折叠表达式 (C++17)、索引序列 (C++14)、lambda 函数和模板参数包(均为 C++11)的非递归 C++17 方式解决方案:

#include <tuple>
#include <iostream>
#include <ostream>
#include <utility>

template< typename F, typename ...types >
F
for_all(F f, types &&... values)
{
    (f(std::forward< types >(values)), ...);
    return std::move(f);
}

template< typename F, typename ...types, std::size_t ...indices >
F
for_all_indices(F f, std::tuple< types... > const & t, std::index_sequence< indices... >)
{
    return for_all(std::move(f), std::get< indices >(t)...);
}

template< typename first, typename ...rest > // non-nullary tuples only
std::ostream &
operator << (std::ostream & out, std::tuple< first, rest... > const & t)
{
    //return ((out << std::get< first >(t)) << ... << std::get< rest >(t)); // simply prints extracted tuple elements w/o delimiters
    out << '[';
    for_all_indices([&out] (auto const & value) { out << value << ", "; }, t, std::index_sequence_for< rest... >{});
    return out << std::get< sizeof...(rest) >(t) << ']';
}

int
main()
{
    std::cout << std::make_tuple(1, 2.F, 3.0) << std::endl;
    return 0;
}

现场演示


1
投票

您可能不需要 C++17(尚未发布)来获得非递归(实际上是递归,但以更自然的方式)解决方案。也就是说,你不需要折叠表达式,只需要索引序列(C++14)和模板参数包(C++11)。

#include <iostream>
#include <sstream>
#include <utility>
#include <tuple>
#include <string>

template<class T>
std::ostringstream& concat_to_stream(std::ostringstream &oss, T &&arg) {
  oss << arg;
  return oss;
}

template<class First, class ...Rest>
std::ostringstream& concat_to_stream(std::ostringstream &oss, First &&firstArg, Rest &&... restArgs) {
  oss << firstArg << ", ";
  return concat_to_stream(oss, std::forward<Rest &&>(restArgs)...);
}

template<class ...Types>
std::string concat_to_string(Types &&... args) {
  std::ostringstream oss;
  oss << '[';
  concat_to_stream(oss, std::forward<Types &&>(args)...);
  oss << ']';
  return oss.str();
}

template<class Tuple, size_t... Indices>
std::string help_concat(const Tuple &tuple, std::index_sequence<Indices...>) {
  return concat_to_string(std::get<Indices>(tuple)...);
};

template<class ...Types>
std::string tuple_to_string(const std::tuple<Types...> &tuple) {
  return help_concat(tuple, std::make_index_sequence<sizeof...(Types)>{});
};

template<class ...Types>
std::ostream &operator<<(std::ostream &os, const std::tuple<Types...> &tuple) {
  return os << tuple_to_string(tuple);
}

int main() {
  std::tuple<int, double, std::string> sample_tuple = std::make_tuple(3, 1.723, "Hi!");
  std::cout << sample_tuple << '\n'; // [3, 1.723, Hi!]
  return 0;
}

递归部分是

concat_to_stream
部分,这是很自然和常见的。关键部分是
help_concat
,我从Implementing std::tuple From The Ground Up: Part 6, tuple_cat Take 1.

技术是在参数列表中使用一个虚拟的

std::index_sequence
来推导模板参数列表中的
size_t... Indices
,允许我们将
std::tuple
的内容“扁平化”为可变参数列表,可以被
concat_to_string
功能。


0
投票

这里是一个非递归版本,通过使用 std::integer_sequence 和一些其他相关技术。

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple_impl(std::basic_ostream<Ch,Tr>& os,
                      const Tuple& t,
                      std::index_sequence<Is...>)
{
    using swallow = int[];
    (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
}

template<class Ch, class Tr, class... Args>
decltype(auto) operator<<(std::basic_ostream<Ch, Tr>& os,
                          const std::tuple<Args...>& t)
{
    os << "(";
    print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
    return os << ")";
}

最初来自这里: http://en.cppreference.com/w/cpp/utility/integer_sequence


0
投票

C++17 解决方案。

namespace std {

// Print tuple to ostream.
template<typename... Args>
ostream& operator<<(ostream& os, tuple<Args...> const& t)
{
  bool first = true;
  apply([&](auto&&... args){ ((os << (first ? "" : ", ") << args, first = false), ...); }, t);
  return os;
}

} // namespace std

这必须放在命名空间 std 中才能使 ADL 工作。 事实上每个运营商<< for a type in namespace N should be defined in namespace N. In that case ADL will cause the compiler to find it.

需要标题

#include <iostream>
#include <tuple>

在线示例这里.


0
投票

有一个巧妙的技巧可以让您将元组的所有元素作为单独的变量获取。这允许你这样写:

#include <iostream>
#include <cstdint>
#include <sstream>
using namespace std;

template<typename... Args>
istream& operator>>( istream& in, tuple<Args...>& tup ) {
    auto func = [&]( auto&... args ) {
        ( in >> ... >> args );
    };
    apply( func, tup );
    return in;
}

template<typename... Args>
ostream& operator<<( ostream& out, const tuple<Args...>& tup ) {    
    auto func = [&]( const auto&... args ) {
        out << "{ ";
        ( (out << args << ", " ), ... );
        out << " }";
    };
    apply( func, tup );
    return out;
}

int main() {
    auto data = "Hello 42"s;
    stringstream in(data);
    tuple<string, uint32_t> tup;
    in >> tup;
    cout << "The tuple: " << tup << '\n';
}
© www.soinside.com 2019 - 2024. All rights reserved.