I was under the (possibly incorrect) assumption that non-member functions in C++ do not dispatch based on the type of its arguments. But after reading about iterator_category it seems I can call a function depending on the category type of its argument and the call also handles inheritance. For instance if I write only the implementations for random access iterator and input iterator, all calls with a non-random access iterator will go to the function that accepts input iterator. This is a shortened example from the book
template <class T>
foo(T a) {
foo(a, iterator_traits<T>::iterator_category());
}
template <class T>
foo(T a, random_access_iterator_tag) { \\body}
template <class T>
foo(T a, input_iterator_tag) { \\body}
// presumably this works even for
// ostream_iterator<int> i(cout);
// foo(i);
Is this kind of dispatch generally true or is this a special case ?
Is the compiler supposed to warn me if my implementations are not exhaustive, for example in the iterator category based example, if I gave an implementation for random access iterator and bidirectional iterator only, should the compiler complain that output iterator is not covered.
This is also the first time I encountered a function with an argument that is only a type, and not an object/instance. So can I define functions with built-in or user-defined types as one of its arguments without specifying the name of the instance/object of that type ?
The following seems to be an alternative to CRTP to achieve compile time polymorphism. Is that a correct interpretation
template<class T>
int foo(T a) {
foo(a, a::type());
}
int foo(int a, base) { \\body }
int foo(int a, derived) { \\body }
You are indeed using compile-time polymorphism, which dispatches based on the static type of the object.
The iterator categories are chained by inheritance (not the iterator themselves), so that:
(should be an
OutputIteratortoo, but it does not matter here)Using
iterator_traits, you can retrieve the iterator category associated with the current iterator. You create a dummy value, and then the overload resolution process kicks in. Suppose for the sake of example that you have 3 overloads:Now suppose that I use
foowith a list iterator:Then, the 4
fooare found in the scope (name resolution).foo(T)is immediately discarded (wrong number of argument)foo(T, std::random_access_iterator_tag const&)is discarded because there is no conversion fromstd::bidirectional_iterator_tagtostd::random_access_iterator_tag.This leaves 2
fooboth being compatible (note: if we had used an OutputIterator, we would not have anything left, and a compilation error would be raised at this point).We then finally get into the ranking part of the overload resolution process. Since
std::forward_iterator_tagis a “closer” (more immediate) base thanstd::input_iterator_tag, it is therefore ranked higher.foo(T, std::forward_iterator_tag const&)is selected.Note the static portion of this though.
Here, even though the
tagreally is (dynamic) astd::random_access_iterator_tag, it is seen by the system as astd::forward_iterator_tag, and thus the same overload that above will be selected.