[英]How to write a streaming 'operator<<' that can take arbitary containers (of type 'X')?
我有一個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
”是滿足上述注釋描述的任何類型。)
如果您之前已閱讀此答案,則可能需要向下滾動到下面的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";
}
它只檢測看起來有點像int
東西和具有不同重載的double
容器。 我建議改變operator<<
的實現。 ;)
一個更合適的路線(感謝@Xeo)就是這個adl-hack。 我們創建一個輔助命名空間,我們從std
導入begin
和end
,然后是一些模板函數,它們在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
的數組作為容器的數量超過double
,所以在其命名空間中你定義一個begin
和end
函數,它返回迭代器,使得容器作為參數也起作用。 這匹配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<<
overloads就是你想要的。
我假設類型為T
的容器是任何具有typedef iterator
類,其value_type
是T
這將涵蓋每個std
容器和大多數自定義容器。
鏈接到執行運行: http : //ideone.com/lMUF4i - 請注意,某些編譯器不支持完整的C ++ 11 SFINAE,並且可能需要使用tomfoolery才能使其正常工作。
留下的測試用例可幫助某人檢查編譯器對這些技術的支持程度。
template<template<class T, class A> class container>
std::ostream& opertaor << ( std::ostream&, const container<X, std::allocator<X> > &)
{
}
如果您的實現向量,列表等具有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;
}
如果您稍微重新定義問題,為任何提供基於范圍的Widget訪問的類提供特殊的流行為,而不是所有Widget容器的特殊行為,一個解決方案是:
template <class Container>
std::ostream& operator << (std::ostream &out, const Container &container)
{
for(const Widget& c : container) {
out << c;
out.put(' ');
}
return out;
}
這適用於std::vector
, std::list
, std::deque
和std::set
。 如果您嘗試流式傳輸不提供Widget范圍訪問權限的內容,比如std::list<int>
,則會出現編譯錯誤,因為const Widget引用無法綁定到std::list<int>
。 如果為operator << for std::list<int>
提供重載,代碼將編譯。
雖然@razeh有一個很好的解決方案,但是如果你需要得到花哨的並且專門打印X
容器和Y
容器,你可以執行以下操作:
// 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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.