1

I'm writing a class IteratorIterable that wraps a container class (a class with begin() and end() methods returning some iterators) to make it possible to iterate over the iterators of the wrapped class. The idea is based on this post. My code looks like this (some methods left out for brevity):

template <class T>
class IteratorIterable
{
private:
    T& container;

public:
    typedef decltype(container.begin()) BackendIterator;

public:
    class IteratorIterator
    {
    public:
        IteratorIterator() : it() {}
        IteratorIterator(BackendIterator it) : it(it) {}
        IteratorIterator(const IteratorIterator& other) : it(other.it) {}

        IteratorIterator& operator=(const IteratorIterator& other) { if (&other == this) return *this; it = other.it; return *this; }

        BackendIterator operator*() const { return it; }
        const BackendIterator* operator->() const { return &it; }

        bool operator==(const IteratorIterator& other) { return it == other.it; }
        bool operator !=(const IteratorIterator& other) { return it != other.it; }

        IteratorIterator operator+(size_t n) { return IteratorIterator(it + n); }

        IteratorIterator& operator++() { ++it; return *this; }
        IteratorIterator operator++(int) { IteratorIterator cpy(*this); ++(*this); return cpy; }

    private:
        BackendIterator it;
    };

public:
    IteratorIterable(T& container) : container(container) {}

    IteratorIterator begin() const { return IteratorIterator(container.begin()); }
    IteratorIterator end() const { return IteratorIterator(container.end()); }
};


template <class T>
IteratorIterable<T> ItIt(T& container)
{
    return IteratorIterable<T>(container);
}

The problem here is, the operator+() method in IteratorIterator is only valid for a random-access BackendIterator, because otherwise the addition operator is not defined on the backend. I want my IteratorIterator to provide this method only if the backend supports it.

Consider this example code:

typedef list<int> Cont;

Cont vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

IteratorIterable<Cont> itb(vec);

IteratorIterable<Cont>::IteratorIterator beg = itb.begin();
IteratorIterable<Cont>::IteratorIterator it = beg;

it++;
//it = beg+1;

printf("%d\n", **it);

This compiles fine when using the it++ line, but - as expected - fails with the it = beg+1 line, because list<int>::iterator is not random-access. I guess this is because if I don't actually instantiate IteratorIterator::operator+(), the compiler doesn't care.

I know that templates are allowed to be valid only for some template arguments, but here the class is templated, not the method. Is it correct to instantiate a class template where one of the methods is invalid when that method is never used for this particular instantiation? GCC and Clang don't complain, but is it correct as per the C++ standard?

Community
  • 1
  • 1
Alemarius Nexus
  • 340
  • 3
  • 12

2 Answers2

4

Yes, the standard guarantees that implicit instantiation of a class template only causes implicit instantiation of the declarations, and not definitions of the member functions.

§14.7.1 [temp.inst]

1   ... The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions.

11   An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, or a static data member of a class template that does not require instantiation....

Note that your code will fail to compile whether you use operator+ or not if you explicitly instantiate the class

template class IteratorIterable<std::list<int>>;

You can prevent that by using enable_if to SFINAE the member function from the overload set unless BackendIterator is a random access iterator.

template<typename Iter = BackendIterator>
typename std::enable_if<
    std::is_same<typename std::iterator_traits<Iter>::iterator_category,
                 std::random_access_iterator_tag>::value,
    IteratorIterator>::type
operator+(size_t n) 
{
    return IteratorIterator(it + n); 
}

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • @T.C. Thank you, that's even better than the one I quoted – Praetorian Aug 20 '14 at 05:30
  • Not sure about that `static_assert` with parameter pack trick - 14.6/p8 "If every valid specialization of a variadic template requires an empty template parameter pack, the template is ill-formed, no diagnostic required." – T.C. Aug 20 '14 at 17:58
  • @T.C. Well, TIL. I've seen a few other answers where people have used that to prevent unwanted specialization. I've gotten rid of it, thanks for pointing it out. – Praetorian Aug 20 '14 at 18:11
1

Yes, it's fine. Substitution failure is not an error (SFINAE).

John Zwinck
  • 239,568
  • 38
  • 324
  • 436