Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to print generic std::list iterator?

I want to be able to print any std::list iterator by printing its value. My initial code looked like this:

template<typename T>
std::ostream& operator<<(std::ostream& os, const typename std::list<T>::const_iterator& x)
{
   return os << "&" << *x;
}

Which did not work, since the compiler can not determine the parameter T. I then tried making it generic over the iterator type itself and using iterator_traits to limit it to iterators.

template<
    typename It,
    typename = typename std::iterator_traits<It>::value_type
>
std::ostream &operator<<(std::ostream &os, const It &x)
{
    return os << "&" << *x;
}

But then, of course, I get two conflicting implementations for std::ostream << *const char, since pointers are also iterators. How can I limit the implementation to std::list iterators so I don't get a conflict?

like image 749
madlaina Avatar asked Aug 12 '20 09:08

madlaina


2 Answers

You can constrain the type as iterator or const_iterator of std::list. E.g.

template<typename It>
std::enable_if_t<std::is_same_v<It, typename std::list<typename std::iterator_traits<It>::value_type>::iterator> ||
                 std::is_same_v<It, typename std::list<typename std::iterator_traits<It>::value_type>::const_iterator>
                 , std::ostream &> 
operator<<(std::ostream &os, const It &x) {
    return os << "&" << *x;
}
like image 87
songyuanyao Avatar answered Nov 15 '22 00:11

songyuanyao


You can SFINAE the const char* out from the operator<< overload.

#include <type_traits> // std::enable_if_t, std::is_same_v, std::remove_reference_t

template<
    typename It,
    typename = typename std::iterator_traits<It>::value_type
>
auto operator<<(std::ostream &os, const It &x)
-> std::enable_if_t< !std::is_same_v<std::remove_reference_t<It>, const char*>, std::ostream&>
{
    return os << "&" << *x;
}

(See a Demo)

Note that, the above is not only restricted for std::list::iterator, meaning the iterators from the other containers, can also consider this overload. This might not be the behaviour you want.


Since we could not get the container type from the iterator, I would suggest the same as @super mentioned in the comments. Provide a operator<< overload for the Legacy Bidirectional Iterator which is what the std::list has.

Following is an example code, which will work for your expected cases as well as all the containers, which meet the requirements of a bidirectional iterator.

#include <list>
#include <iostream>
#include <iterator>    // std::iterator_traits, std::bidirectional_iterator_tag
#include <type_traits> // std::is_same_v, std::enable_if_t

// SFINAE helper  type for bidirectional_iterator_t
template<typename Iterator, typename ReType = void>
using enable_for_bidirectional_iterator_t
= std::enable_if_t<
   std::is_same_v<std::bidirectional_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category>
   , ReType
>;

template<typename Iterator>
auto operator<<(std::ostream& os, const Iterator x) noexcept
-> enable_for_bidirectional_iterator_t<Iterator, std::ostream&>
{
   return os << "&" << *x;
}

(See a Demo)


However, usually, you provide an operator<< overload for the container, not for the iterators. You might want to rethink about the design.

like image 30
JeJo Avatar answered Nov 15 '22 00:11

JeJo