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?
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With