如何编写一个流'运算符<<',它可以采用任意容器(类型'X')?

Dre*_*ann 9 c++ templates iostream stl

我有一个C++类" X",如果它们的容器发送到一个容器,它将具有特殊意义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
}
Run Code Online (Sandbox Code Playgroud)

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

有没有办法一般编码operator << ( std::ostream &, const Container & )

(" Container"这里将是满足上述注释说明的任何类型.)

Yak*_*ont 5

如果您之前已阅读此答案,则可能需要向下滚动到下面的ADL版本.它有很大的改进.

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

#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";
}
Run Code Online (Sandbox Code Playgroud)

它只检测看起来有点像的东西intdouble具有不同重载的容器.我建议改变实施operator<<.;)

一个更合适的路线(感谢@Xeo)就是这个adl-hack.我们创建一个辅助命名空间,我们在其中导入beginendstd,然后一些模板函数执行参数依赖查找beginend(std如果我们没有更严格的绑定,则查看版本),然后使用这些aux::adl_begin函数来确定我们是什么传入可以被视为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";
}
Run Code Online (Sandbox Code Playgroud)

有了这个,不仅doubles的数组作为容器计数double,所以在你的命名空间中你定义一个beginend函数,返回迭代器,使得容器作为参数也起作用.这与for(auto&& i:container)查找的工作方式相匹配(完美吗?相当不错?),因此是"容器"的良好工作定义.

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

...

这里是原始的短代码及其周围的一些测试框架:(如果你的编译器抛出它,你会看到下面出错的地方很有用)

#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();
}
Run Code Online (Sandbox Code Playgroud)

大约一半的是测试样板.该is_iterator_of_type模板和operator<<重载是你想要什么.

我假设类型的容器T是任何具有typedef的类iterator,其类型value_type为a T.这将涵盖每个std容器和大多数自定义容器.

链接到执行运行:http://ideone.com/lMUF4i - 请注意,某些编译器不支持完整的C++ 11 SFINAE,并且可能需要使用tomfoolery才能使其正常工作.

留下的测试用例可帮助某人检查编译器对这些技术的支持程度.