关于c ++:Pretty-print std :: tuple

Pretty-print std::tuple

这是我上一个关于漂亮印刷STL容器问题的后续问题,为此,我们设法开发了一个非常优雅和全面的解决方案。

在下一个步骤中,我希望使用可变模板(包括这是严格的C++ 11),为EDCOX1(0)提供漂亮的打印。对于std::pair,我只是说

1
2
3
4
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
  return o <<"(" << p.first <<"," << p.second <<")";
}

打印元组的类似结构是什么?

我尝试过各种各样的模板参数栈解包,传递索引,并使用sfinae发现我在最后一个元素的时间,但是没有成功。我不会用我的坏代码给您带来负担;希望问题描述足够直接。从本质上讲,我喜欢以下行为:

1
2
auto a = std::make_tuple(5,"Hello", -0.1);
std::cout << a << std::endl; // prints: (5,"Hello", -0.1)

与前一个问题具有相同的通用性级别(char/wchar_t,对定界符)的额外分数!


YAY,指数~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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上的实时示例。

对于分隔符,只需添加以下部分专用化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

1
2
3
4
5
6
7
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)...};
}


我在C++ 11(GCC 4.7)中完成了这个工作。我确信我还没有考虑到一些陷阱,但我认为代码容易阅读而且不复杂。唯一可能奇怪的是"guard"结构tuple打印机,它确保在到达最后一个元素时终止。另一个奇怪的事情可能是返回类型包中类型数的sizeof…(types)。它用于确定最后一个元素的索引(大小…(类型)-1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}


我很惊讶CPPreference上的实现还没有发布在这里,所以我会为子孙后代这样做。它隐藏在std::tuple_cat的文档中,因此不容易找到。它使用了一个类似于这里其他一些解决方案的保护结构,但我认为它们最终更简单,更容易理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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 <<")
"
;
}
// end helper function

测试:

1
2
3
4
5
6
7
8
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, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

现场演示


在C++ 17中,我们可以利用折叠表达式来实现这一点,尤其是一元左折叠:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<class TupType, size_t... I>
void print(const TupType& _tup, std::index_sequence<I...>)
{
    std::cout <<"(";
    (..., (std::cout << (I == 0?"" :",") << std::get(_tup)));
    std::cout <<")
"
;
}

template<class... T>
void print (const std::tuple<T...>& _tup)
{
    print(_tup, std::make_index_sequence<sizeof...(T)>());
}

现场演示输出:

(5, Hello, -0.1)

鉴于

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

解释

我们的一元左折是这样的

1
... op pack

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

1
(..., (std::cout << std::get(myTuple))

所以如果我有一个这样的元组:

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

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

1
size_t... I

然后是表达式

1
(..., (std::cout << std::get(myTuple))

扩展到

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

哪个会打印

5Hello-0.1

这是很粗糙的,所以我们需要做更多的技巧来添加一个逗号分隔符,除非它是第一个元素。

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

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

现在我们可以

5, Hello, -0.1

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

*注意:你可以用不同的方式来做逗号分隔,而不是我最后的结果。我最初是有条件地在后面添加逗号,而不是在前面通过测试std::tuple_size::value - 1,但这太长了,所以我测试了sizeof...(I) - 1,但最后我复制了xeo,结果得到了我所得到的。


基于BJARNE StruouTutp的C++编程语言,第817页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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;
    }

输出:

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

基于ANDYG代码的C++ 17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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(_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;
}

输出:

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


另一个类似于@tony olsson的,包括空元组的专门化,如@kerrek sb所建议的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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(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 <<")";
}

下面是另一个实现:

网址:https://github.com/galaxyeye/atlas/blob/master/atlas/io/tuple.h

测试代码:

https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/tuple.cpp

享受: