如何写一个流式操作符<<可以取任意容器(类型'X')? [英] How to write a streaming 'operator<<' that can take arbitary containers (of type 'X')?

查看:131
本文介绍了如何写一个流式操作符<<可以取任意容器(类型'X')?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个C ++类 X ,如果 一个容器 发送到 std :: ostream

I have a C++ class "X" which would have special meaning if a container of them were to be sent to a std::ostream.

我最初实现它专门为 std :: vector< X>

std::ostream& operator << ( std::ostream &os, const std::vector<X> &c )
{
   // The specialized logic here expects c to be a "container" in simple
   // terms - only that c.begin() and c.end() return input iterators to X
}


b $ b

如果我想支持 std :: ostream<< std :: deque< X> std :: ostream< std :: set< X> 或任何类似的容器类型,我知道的唯一解决方案是复制粘贴整个函数,只更改函数签名!

If I wanted to support std::ostream << std::deque<X> or std::ostream << std::set<X> or any similar container type, the only solution I know of is to copy-paste the entire function and change only the function signature!

有一种方法可以对运算符<< (std :: ostream& const Container&)

Container

("Container" here would be any type that satisfies the commented description above.)

推荐答案

如果你以前阅读过这个答案,可能需要向下滚动到下面的ADL版本。

If you have read this answer before, you might want to scroll down to the ADL version below. It is much improved.

首先,一个简单而甜蜜的版本,非常有效:

First, a short and sweet version that pretty much works:

#include <iostream>
#include <type_traits>
template<typename T, typename Iterator, typename=void>
struct is_iterator_of_type: std::false_type {};

template<typename T, typename Iterator>
struct is_iterator_of_type<
  T,
  Iterator,
  typename std::enable_if<
    std::is_same<
      T,
      typename std::iterator_traits< Iterator >::value_type
    >::value
  >::type
>: std::true_type {};

template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
  typename std::enable_if< is_iterator_of_type<int, typename Container::iterator>::value, std::ostream& >::type
{
  return stream << "int container\n";
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
  typename std::enable_if< is_iterator_of_type<double, typename Container::iterator>::value, std::ostream& >::type
{
  return stream << "double container\n";
}

这只是检测类似 int double 具有不同重载的容器。我建议更改运算符<< 的实现。 ;)

which merely detects things that look sort of like int and double containers with distinct overloads. I would advise changing the implementation of operator<<. ;)

一个更合适的路由(感谢@Xeo)将是这个adl-hack。我们创建一个辅助命名空间,在其中从 std begin end c>,然后一些对开始和结束进行参数依赖查找的模板函数> std version如果我们没有更紧密的绑定),然后使用这些 aux :: adl_begin 函数来确定我们传递进来可以作为一个容器在X:

A more proper route (thanks @Xeo) would be this adl-hack. We create an auxiliary namespace where we import begin and end from std, then some template functions that do argument dependent lookup on begin and end (seeing the std version if we don't have a more tightly bound one), and then use these aux::adl_begin functions to determine if what we are passed in can be treated as a container over X:

#include <iostream>
#include <vector>
#include <type_traits>
#include <iterator>
#include <set>

template<typename T, typename Iterator, typename=void>
struct is_iterator_of_type: std::false_type {};

template<typename T, typename Iterator>
struct is_iterator_of_type<
  T,
  Iterator,
  typename std::enable_if<
    std::is_same<
      T,
      typename std::iterator_traits< Iterator >::value_type
    >::value
  >::type
>: std::true_type {};

namespace aux {
  using std::begin;
  using std::end;
  template<class T>
  auto adl_begin(T&& v) -> decltype(begin(std::forward<T>(v))); // no implementation
  template<class T>
  auto adl_end(T&& v) -> decltype(end(std::forward<T>(v))); // no implementation
}

template<typename T, typename Container, typename=void>
struct is_container_of_type: std::false_type {};

template<typename T, typename Container>
struct is_container_of_type<
  T,
  Container,
  typename std::enable_if<
    // we only want this to be used if we iterable over doubles:
    is_iterator_of_type<
      T,
      decltype(void(aux::adl_begin(*(Container*)nullptr)), aux::adl_end(*(Container*)nullptr)) // ensure being and end work as bonus
    >::value
  >::type
>: std::true_type
{};

template<class Ch, class Tr, class Container>
auto operator<<( std::basic_ostream<Ch,Tr>& stream, Container const& c ) ->
  typename std::enable_if<
    is_container_of_type<double, Container>::value,
    decltype(stream)
  >::type
{
  stream << "'double' container: [ ";
  for(auto&& e:c)
    stream << e << " ";
  return stream << "]";
}

int main() {
  std::cout << std::vector<double>{1,2,3} << "\n";
  std::cout << std::set<double>{3.14,2.7,-10} << "\n";
  double array[] = {2.5, 3.14, 5.0};
  std::cout << array << "\n";
}

/ code>开始开始 计数为 double end 函数返回迭代器超过double的容器作为参数也可以工作。这匹配如何 for(auto&& i:container)查找工作(完全合理的?),所以是一个很好的工作定义容器。

With this, not only do arrays of doubles count as containers over double, so does anything where in its namespace you define a begin and end function that returns iterators over double that takes the container as an argument also works. This matches how for(auto&& i:container) lookup works (perfectly? reasonably well?), so is a good working definition of "container".

但是,请注意,随着我们添加更多的这些装饰,更少的当前编译器拥有我们使用的所有C ++ 11功能。上面的编译gcc 4.6我相信,但不是gcc 4.5。*。

Note, however, that as we add more of these embellishments, fewer current compilers have all of the C++11 features we are using. The above compiles in gcc 4.6 I believe, but not gcc 4.5.*.

...

这里是原始的短代码与一些测试框架周围它:(有用的,如果你的编译器抛出它,你可以看到它在哪里出错了)

And here is the original short code with some testing framework around it: (useful if your compiler throws it up, you can see where it goes wrong below)

#include <iostream>
#include <type_traits>
#include <vector>
#include <iostream>
#include <set>

template<typename T, typename Iterator, typename=void>
struct is_iterator_of_type: std::false_type {};

template<typename T, typename Iterator>
struct is_iterator_of_type<
  T,
  Iterator,
  typename std::enable_if<
    std::is_same<
      T,
      typename std::iterator_traits< Iterator >::value_type
    >::value
  >::type
>: std::true_type {};

void test1() {
  std::cout << is_iterator_of_type<int, std::vector<int>::iterator>::value << "\n";
}
template<typename T, typename Container>
auto foo(Container const&) -> typename std::enable_if< is_iterator_of_type<T, typename Container::iterator>::value >::type
{
  std::cout << "Container of int\n";
}
template<typename T>
void foo(...)
{
  std::cout << "No match\n";
}
void test2() {
  std::vector<int> test;
  foo<int>(test);
  foo<int>(test.begin());
  foo<int>(std::set<int>());
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
  typename std::enable_if< is_iterator_of_type<int, typename Container::iterator>::value, std::ostream& >::type
{
  return stream << "int container\n";
}
void test3() {
  std::vector<int> test;
  std::cout << test;
  std::set<int> bar;
  std::cout << bar;
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
  typename std::enable_if< is_iterator_of_type<double, typename Container::iterator>::value, std::ostream& >::type
{
  return stream << "double container\n";
}
void test4() {
  std::vector<int> test;
  std::cout << test;
  std::set<int> bar;
  std::cout << bar;
  std::vector<double> dtest;
  std::cout << dtest;
}
void test5() {
  std::vector<bool> test;
  // does not compile (naturally):
  // std::cout << test;
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
  typename std::enable_if< is_iterator_of_type<bool, typename Container::iterator>::value, std::ostream& >::type
{
  return stream << "bool container\n";
}
void test6() {
  std::vector<bool> test;
  // now compiles:
  std::cout << test;
}
int main() {
  test1();
  test2();
  test3();
  test4();
  test5();
  test6();
}

is_iterator_of_type 模板,运算符<< 重载是你想要的。

about half of the above is testing boilerplate. The is_iterator_of_type template, and the operator<< overloads are what you want.

我假设类型 T 的容器是具有typedef 迭代器的任何类 value_type T 。这将覆盖每个 std 容器和大多数自定义容器。

I am presuming that a container of type T is any class with a typedef iterator which whose value_type is a T. This will cover every std container, and most custom ones.

链接到执行运行: http://ideone.com/lMUF4i - 请注意,一些编译器不支持完整的C ++ 11 SFINAE,可能需要tomfoolery

Link to execution run: http://ideone.com/lMUF4i -- note that some compilers don't support full C++11 SFINAE, and may require tomfoolery to get it to work.

测试用例,帮助某人检查编译器对这些技术的支持程度。

Test cases left in to help someone check what level of support their compiler has for these techniques.

这篇关于如何写一个流式操作符&lt;&lt;可以取任意容器(类型'X')?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆