From: Alex Chovanec (achovane_at_[hidden])
Date: 2004-08-23 23:17:12


"David Abrahams" <dave_at_[hidden]> wrote in message
news:uacwmy7tw.fsf_at_boost-consulting.com...
> "Alex Chovanec" <achovane_at_[hidden]> writes:
>
> >
> > So 'is_iterator<int *>::value' evaluates to true without generating a
> > compile error, as it should.
>
> *Any* iterator can be implemented the same way. An iterator class
> does not have to have a value_type member!

Ok, you bring up a valid point. I would like to suggest a possible solution
to this problem, but the explanation is a bit long, so please bear with me.
:-)

Originally, I was using the following SFINAE approach in 'is_iterator' to
detect member iterator traits:

    template <typename T>
    static char test(
        T *,
        type_tag<typename T::value_type> * = 0,
        type_tag<typename T::reference> * = 0,
        type_tag<typename T::pointer> * = 0,
        type_tag<typename T::difference_type> * = 0,
        type_tag<typename T::iterator_category> * = 0
    );
    static char (& test(...))[2];

I had considered using Boost iterator traits to do the same thing, like
this:

    template <typename T>
    static char test(
        T *,
        type_tag<typename detail::iterator_traits<T>::value_type> * = 0,
        type_tag<typename detail::iterator_traits<T>::reference> * = 0,
        type_tag<typename detail::iterator_traits<T>::pointer> * = 0,
        type_tag<typename detail::iterator_traits<T>::difference_type> * =
0,
        type_tag<typename detail::iterator_traits<T>::iterator_category> * =
0
    );
    static char (& test(...))[2];

But I felt that the first approach was clearer, and I reasoned that it would
be ok to rely on member traits if I also defined a partial template
specialization to handle pointers.

It wasn't until I read your last email that I tried the second approach and
realized that it doesn't work. It's no surprise that attempting to
instantiate 'iterator_traits<T>' causes a compile error when 'T' is not an
iterator, but I had assumed that SFINAE would simply remove this overload of
'test' from the set of candidate functions. I see now that SFINAE doesn't
work unless you have a valid type on the lefthand side of the '::' operator.

So the problem is not that 'iterator_traits<T>::value_type' does not exist;
the problem is that 'iterator_traits<T>' cannot be instantiated. But what if
'iterator_traits<T>' were defined in such a way that it can still be
instantiated even if 'T' is not an iterator? If 'T' is not an iterator, then
'iterator_traits<T>' can simply be defined to be without any member types.
This is easy to do with a little template metaprogramming. The modified
primary template for Boost iterator traits might look something like this:

    template <typename T>
    struct iterator_traits : public
        mpl::if_<
            mpl::or_<
                has_member_iterator_traits<T>,
                is_pointer<T> >,
            std::iterator_traits<T>,
            empty_iterator_traits
>::type
    {};

Here, 'has_member_iterator_traits' implements my first approach for
detecting member iterator traits, and 'empty_iterator_traits' is just an
empty type. This definition works for both iterators that have member traits
and iterators that are pointers; and it can be specialized for other
iterators just as easily as the existing 'iterator_traits'.

Now, 'is_iterator' can be defined in terms of Boost iterator traits using
the second approach described above. Since 'iterator_traits<T>' can always
be instantiated, SFINAE will take care of the rest, and we won't be bothered
with unwanted compile errors.

What are your thoughts on this approach?

Thanks,

Alex Chovanec