9

私は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
}

std::ostream << std::deque<X>または同様のコンテナタイプをサポートしたい場合std::ostream << std::set<X>、私が知っている唯一の解決策は、関数全体をコピーして貼り付け、関数のシグネチャのみを変更することです。

一般的にコーディングする方法はありますoperator << ( std::ostream &, const Container & )か?

(" Container"ここでは、上記のコメント付きの説明を満たす任意のタイプになります。)

4

5 に答える 5

6

以前にこの回答を読んだことがある場合は、下の 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";
}

これは、似ているものintdouble明確なオーバーロードを持つコンテナを検出するだけです。の実装を変更することをお勧めしoperator<<ます。;)

より適切なルート (@Xeo に感謝) は、この adl-hack です。beginandendからをインポートする補助名前空間を作成し、std引数に依存する and を検索するいくつかのテンプレート関数を作成しbegin(endより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";
}

doubleこれにより、 s の配列が のコンテナとしてカウントされるだけでなくdouble、その名前空間でコンテナを引数として取る 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();
}

上記の約半分はボイラープレートのテストです。is_iterator_of_typeテンプレートとオーバーoperator<<ロードはあなたが望むものです。

type のコンテナはT、.typedefiteratorを持つクラスでvalue_typeあると想定していますTstdこれは、すべてのコンテナーとほとんどのカスタム コンテナーをカバーします。

実行へのリンク: http://ideone.com/lMUF4i -- 一部のコンパイラは完全な C++11 SFINAE をサポートしておらず、動作させるために tomfoolery が必要になる場合があることに注意してください。

テストケースは、コンパイラがこれらの手法に対してどのレベルのサポートを提供しているかを誰かが確認するのに役立つように残されています。

于 2012-12-07T20:26:23.033 に答える
3
template<template<class T, class A> class container>
std::ostream& opertaor << ( std::ostream&, const container<X, std::allocator<X> > &)
{
}

実装ベクトル、リストなどに 2 つ以上のテンプレート パラメーターがある場合、これは機能しません。

于 2012-12-05T14:14:56.323 に答える
2

エレガントではないにしてもシンプルです。次にコードを保守する人は、派手なテンプレートがないことを高く評価するかもしれません! 実際には、cpp、または少なくともDetail名前空間で「Print」メソッドを非表示にします。

#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <set>
#include <multiset>

class X {};

template <typename T>
std::ostream& Print(std::ostream& os, const T& container)
{
    for(auto ii = container.cbegin(); ii != container.cend(); ++ii);
        //etc
        //
    return os;
}

std::ostream& operator<<(std::ostream& os, const std::vector<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::deque<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::list<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::set<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::multiset<X>& v) { return Print(os, v); }

int main()
{
            // Example
    std::vector<X> v;
    std::cout << v;
}
于 2012-12-10T14:06:21.190 に答える
0

すべての Widget コンテナに特別な動作を提供するのではなく、Widget への範囲ベースのアクセスを提供する任意のクラスに特別なストリーミング動作を提供するという質問を少し再定義すると、1 つの解決策は次のようになります。

  template <class Container>
  std::ostream& operator << (std::ostream &out, const Container &container) 
  {
    for(const Widget& c : container) {
      out << c;
      out.put(' ');
    }
    return out;
  }

これはstd::vectorstd::liststd::deque、および で機能しstd::setます。Widget への範囲アクセスを提供しないもの、たとえば をストリーミングしようとするとstd::list<int>、const Widget 参照が の int にバインドできないため、コンパイル エラーが発生しますstd::list<int>。演算子 << にオーバーロードを指定するとstd::list<int>、コードがコンパイルされます。

于 2012-12-10T14:15:21.550 に答える
-1

@razeh には優れた解決策がありますが、手の込んだものが必要で、 のコンテナと のコンテナに特化した印刷が必要な場合XY、次のようにすることができます。

    // Types for which you want specialized streaming of containers
    // We need some identifiable typedef in these types
    struct  X { typedef void X_type; };
    struct  Y { typedef void Y_type; };


    // Wrappers for implementing streaming logic for each type        

template <typename C>
struct WrapX
{
    WrapX(const C& c) : c(c) { }
    const C& c;

    std::ostream& stream(std::ostream& os)
    {
         // Special container of X printing
         return os;
    }
};

template <typename C>
struct WrapY
{
    WrapY(const C& c) : c(c) { }
    const C& c;

    std::ostream& stream(std::ostream& os)
    {
        // Special container of Y printing
        return os;
    }
};

    // Wrap functions, by using a 'dummy' parameter
    // we can get the compiler to select the function based
    // on the incoming type

template <typename C >
WrapX<C> Wrap(const C& c,  typename C::value_type::X_type* = 0) { return WrapX<C>(c); }

template <typename C>
WrapY<C> Wrap(const C& c, typename C::value_type::Y_type* = 0) { return WrapY<C>(c); }



    // Overload - same problem as @razeh solution, this is a VERY generic
    // function and may clash with other declarations. Keep it closely confined to
    // where you need it.
template <typename C>
std::ostream& operator<<(std::ostream& os, const C& c) { return Wrap(c).stream(os);  }




int main()
{
    std::vector<X> vx;
    std::cout << vx;

        std::vector<Y> vy;
        std::cout << vy;
}
于 2012-12-10T14:57:46.200 に答える