I'd like to specialize std::iterator_traits<>
for iterators of a container class template that does not have the usual nested typedefs (like value_type
, difference_type
, etc.) and whose source I shouldn't modify. Basically I'd like to do something like this:
template <typename T> struct iterator_traits<typename Container<T>::iterator>
{
typedef T value_type;
// etc.
};
except that this doesn't work, as the compiler is unable to deduce T
from Container<T>::iterator
.
Is there any working way to achieve the same?
For example:
template <typename T>
class SomeContainerFromAThirdPartyLib
{
typedef T ValueType; // not value_type!
// no difference_type
class iterator
{
typedef T ValueType; // not value_type!
// no difference_type
...
};
iterator begin() { ... }
iterator end() { ... }
...
};
Now suppose I call std::count()
using an instance of this class. As far as I know, in most STL implementations, count()
returns iterator_traits<Iterator>::difference_type
. The primary template of iterator_traits<I>
simply does typedef typename I::difference_type difference_type
. Same with the other nested types.
Now in our example this obviously won't work, as there's no Container::iterator::difference_type
. I thought I could work around this without modifying the iterator class, by specializing iterator_traits
for iterators of any Container<T>
.
In the end, I just want to be able to use std algorithms like count, find, sort, etc., preferably without modifying any existing code. I thought that the whole point of iterator_traits
is exactly that: being able to specify types (like value_type
, diff_type
etc.) for iterator types that do not support them built-in. Unfortunately I can't figure out how to specialize the traits class for all instances of Container<T>
.
Nawaz's answer is likely the right solution for most cases. However, if you're trying to do this for many instantiated
SomeContainerFromAThirdPartyLib<T>
classes and only a few functions (or an unknown number of instantiations but a fixed number of functions, as might happen if you're writing your own library), there's another way.Assume we're given the following (unchangeable) code:
We define an adapter class template containing the necessary
typedef
s foriterator_traits
and specialize it to avoid problems with pointers:Then, for each function we want to be able to call with a
SomeContainerFromAThirdPartyLib::iterator
, we define an overload and use SFINAE:We can then use it as follows:
You can find a runnable example with the required
include
s andusing
s at http://ideone.com/gJyGxU. The output:Unfortunately, there are caveats:
find
,sort
, et cetera). This obviously won't work for functions inalgorithm
that haven't been defined yet.In regards to that last one, the question is in which namespace to put the overload (and how to call the
std
version). Ideally, it would be inThirdPartyLib
so that it could be found by argument-dependant lookup, but I've assumed we can't change that. The next best option is inMyLib
, but then the call has to be qualified or preceded by ausing
. In either case the end-user should either useusing std::count;
or be careful about which calls to qualify withstd::
, since ifstd::count
is mistakenly used withSomeContainerFromAThirdPartyLib::iterator
, it will obviously fail (the whole reason for this exercise).An alternative that I do not suggest but present here for completeness would be to put it directly in the
std
namespace. This would cause undefined behavior; while it might work for you, there's nothing in the standard that guarantees it. If we were specializingcount
instead of overloading it, this would be legal.In the specialization in question,
T
is in a nondeducible context but there is neither a third party library container code change nor any specialization in thestd
namespace required.If the third party library does not provide any free
begin
andend
functions in the respective namespace one can write own functions (into that namespace if desired to enable ADL) and wrap the iterator into an own wrapper class which in turn provides the necessary typedefs and operators.First one needs the Iterator wrapper.
Note: It would also be possible to make
iterator_wrapper
inherit fromIterator
, or to make it more generic and have another helper to enable the wrapping of other iterators as well.Now
begin()
andend()
:(It is also possible to have them in a different namespace than
SomeContainer
but loose ADL. IF there arebegin
andend
functions present in the namespace for that container I'd tend to rename the adaptors to be something likewbegin
andwend
.)The standard algorithms can be called using those functions now:
If
begin()
andend()
are included into the library namespace, the container can even be used in more generic contexts.Such code can be used with
std::vector
as well asThirdPartyLib::SomeContainer
, as long as ADL findsbegin()
andend()
returning the wrapper iterator.You can very well use the
Container
as template parameter to youriterator_traits
. What matters to the rest of STL are the typedefs inside your traits class, such asvalue_type
. Those should be set correctly:You would then use
value_type
where you would previously useT
.As for using the traits class, you of course parametrize it with the type of your external container:
Naturally, this assumes
TheContainer
is conforms to the common STL containers' contract and hasvalue_type
defined correctly.Yes. The compiler cannot deduce
T
fromContainer<T>::iterator
because it is non-deducible context, which in other words means, givenContainer<T>::iterator
, the value ofT
cannot uniquely and reliably be deduced (see this for detail explanation).The only solution to this problem is that you've to fully specialize
iterator_traits
for each possible value ofiterator
which you intend to use in your program. There is no generic solution, as you're not allowed to edit theContainer<T>
class template.