2

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.

111111
  • 15,686
  • 6
  • 47
  • 62
  • 1
    I have some form of `is_container` trait in the [pretty printer](http://stackoverflow.com/questions/4850473/pretty-print-c-stl-containers), if you're interested. – Kerrek SB Feb 11 '12 at 17:16
  • @KerrekSB thanks, I will look over that, however as this is really a test of me getting something like this working I would like to do exactly that and try and get it to work. – 111111 Feb 11 '12 at 17:21
  • This looks unnecessary involved. Try this: http://ideone.com/gBx6P – Johannes Schaub - litb Feb 11 '12 at 18:17
  • @JohannesSchaub-litb: yep that is much better still, I wasn't aware you could do inheritance with the type_traits it is much cleaner. I just need to stop it to binding cases where an operator is already defined. – 111111 Feb 11 '12 at 19:31
  • Maybe that is because `std::set` is not a container in the usual sense. (it violates certain properties that you expect from a container.) Maybe `std::set` is something like a immutable-container (elements cannot be modified). – alfC Mar 19 '16 at 03:37

2 Answers2

4

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) };
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • I was close then, I tried the array thing, but didn't realise it had to be a reference. That is better. Also I was unware that set only had const iterators. I guess I was thrown by this: http://en.cppreference.com/w/cpp/container/set/begin oh well it works now. I don't really think I will bother with the enum bit I am targeting c++11 so I don't thing vc6.0 quirks are a good enough reason to obfuscate my code. Anyway Thanks again. – 111111 Feb 11 '12 at 18:19
  • Well, the `enum` bit isn't so much a quirk for "VC6.0" but a necessity if you ever end up passing the value to something which needs the definition of the value, e.g. a function taking it by `const&`: your `static bool const value = ...` is still **not** a definition although it is given an initial value! If you are using C++2011 you can also define (not just initialize) the value in the class definition by using `static bool constexpr value = ...` (although I'm not entirely sure where the `constexpr` qualify can go, i.e. this notation may be slightly wrong). – Dietmar Kühl Feb 11 '12 at 18:25
  • Actually now that I think about it, I can rip out yes/no for consexpr – 111111 Feb 11 '12 at 18:39
  • @Dietmar are you saying that `static bool constexpr value = ...` in the class definition is a definition instead of only a declaration? That would be new to me. Where can I read about it? – Johannes Schaub - litb Feb 11 '12 at 18:41
  • @JohannesSchaub-litb: no, I'm not saying that `constexpr` in the class definition is a definition. It is still a declaration. However, my understanding is that binding this value to a `const&` doesn't require a definition to be available while doing the same with a `static T const` initialized in the class definition does require a definition to be available. – Dietmar Kühl Feb 11 '12 at 18:50
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, 
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271