问题
I am trying to write a stream operator for std containers, mainly for the purpose of debugging.
I have the following code:
#include <type_traits>
#include <iostream>
#include <ostream>
#include <iterator>
#include <algorithm>
#include <functional>
#include <vector>
#include <set>
#include <deque>
template<typename Container>
struct is_container
{
typedef char no;
typedef long yes;
template<typename A, A, A>
struct is_of_type;
template<typename T>
static yes& is_cont(
is_of_type
<
typename T::iterator(T::*)(),
&T::begin,
&T::end
>*);
template<typename T>
static no& is_cont(...); //any other
template<typename C, bool B>
struct is_class_is_container
{
const static bool value=sizeof( is_cont<C>(nullptr) )==sizeof(yes);
};
template<typename C>
struct is_class_is_container<C, false>
{
const static bool value=false;
};
const static bool value = is_class_is_container
<
Container,
std::is_class<Container>::value
>::value;
};
template<typename T>
typename std::enable_if
< is_container<T>::value, std::ostream >::type&
operator<<(std::ostream& os, const T& a)
{
os << '[';
std::copy(a.begin(), a.end(), std::ostream_iterator<typename T::value_type>(os, ", "));
os << ']';
return os;
}
I am aware this is far from perfect (constructive comments appreciated) but the problem I am having is that it works great for vector, deque and list but fails to match on sets, I don't know why because sets still have the iterator interfaces begin and end.
Thanks.
EDIT: tested on g++ (GCC) 4.6.2 2012012 clang version 3.0
EDIT2: I got it sort of working using decltype however that is sub optimal because now I can't assert that it does what I expect (return an iterator) to.
I don't exactly know what the set was return in the first place, maybe if someone has a way of debugging TMP that would be good.
回答1:
Since std::set<T>
only has one set of immutable iterators, there is only one version of begin()
and end()
which is declared to be const
. That is, the definition of std::set<T>
looks something like this (assuming it was declared in namespace std
before):
template <typename T>
class std::set
{
public:
class iterator;
typedef iterator const_iterator;
...
const_iterator begin() const;
const_iterator end() const;
...
};
The other containers have both a const
and a non-const
version of begin()
and end()
matching the signature you ask for. std::set
doesn't have this. I'm not sure what the easiest work-around for this would be though.
That said, sizeof(char)
is allowed to be sizeof(long)
. The easiest way to guarantee that the yes
and no
types have different size is to use references to arrays of different sizes for the same type, e.g.:
typedef char (&yes)[1];
typedef char (&no)[2];
...
enum { value = sizeof(some_expression) == sizeof(yes) };
回答2:
It works for vector
but not for set
because the latter returns const_iterator
for begin
/end
functions. Change:
typename T::iterator(T::*)(),
to:
typename T::const_iterator(T::*)() const,
来源:https://stackoverflow.com/questions/9242209/is-container-trait-fails-on-stdset-sfinae-issue