Simple Overloading

We discussed in the previous post about how to create type requirements and constraints. However, if we want to overload functions based on type requirements, it can lead to ambiguities using the default overloading mechanism in C++. For example, say we want to implement a version of the std::advance function. This function will advance the iterator using the += operator on random access iterators, but it will use ++ or -- on other iterator types. So lets start by defining are requirements for Incrementable and Advanceable :

struct Incrementable { template<class T> auto requires_(T&& x) -> decltype(++x); }; struct Advanceable { template<class T, class I> auto requires_(T&& x, I&& i) -> decltype(x += i); };

So we can use these to create our advance function. For now, we will assume we are just moving forward(ie the amount to advance is always positive):

template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void advance(Iterator& it, int n) { it += n; } template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void advance(Iterator& it, int n) { while (n--) ++it; }

Now if we call it with an iterator from std::list , it seems to work:

std::list<int> l = { 1, 2, 3, 4, 5, 6 }; auto iterator = l.begin(); advance(iterator, 4); std::cout << *iterator << std::endl;

Which will print out 5 . However, if we use a std::vector instead:

std::vector<int> v = { 1, 2, 3, 4, 5, 6 }; auto iterator = v.begin(); advance(iterator, 4); std::cout << *iterator << std::endl;

We will get a compile error that the call is ambigous, since both overloads are valid calls. This is because the iterators to std::vector are both Incrementable and Advanceable . So we need a way to tell the compiler that the Advanceable version should be preferred over the Incrementable version.

Conditional Overloading

We can use conditional overloading to disambiguate the function calls. Conditional overloading works by trying the first function, and if that fails it tries the next, until it finds a valid function call. This is different then the default function overloading which tries to find the best match. We can think of conditional overloading, like the following code:

template<class Iterator> void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>()) { it += n; } else if (models<Incrementable(Iterator)>()) { while (n--) ++it; }

Of course the above isn’t valid C++ code, but we can emulate its semantics quite easily. Lets start by creating the requirements to detect if a function is callable:

struct Callable { template<class F, class... Ts> auto requires_(F&& f, Ts&&... xs) -> decltype( f(std::forward<Ts>(xs)...) ); };

Now we can use that to create a basic conditional overloading class. Lets start first by just handling two function objects. So our class will have two overloads. The first overload will try to call the first function object. The second overload will try to call the second function object if the first function object can’t be called:

template<class F1, class F2> struct basic_conditional { // We don't need to use a requires clause here because the trailing // `decltype` will constrain the template for us. template<class... Ts> auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...)) { return F1()(std::forward<Ts>(xs)...); } // Here we add a requires clause to make this function callable only if // `F1` is not callable. template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())> auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...)) { return F2()(std::forward<Ts>(xs)...); } };

So now we need to change our functions into function objects:

struct advance_advanceable { template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void operator()(Iterator& it, int n) const { it += n; } }; struct advance_incrementable { template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void operator()(Iterator& it, int n) const { while (n--) ++it; } };

Then we can put them into the basic_conditional class and statically initialize them using the double curly brace {} (global function objects should always be statically initialized to avoid the static initialization order fiasco):

static conditional<advance_advanceable, advance_incrementable> advance = {};

So now if we try to use this function with a vector:

std::vector<int> v = { 1, 2, 3, 4, 5, 6 }; auto iterator = v.begin(); advance(iterator, 4); std::cout << *iterator << std::endl;

This will compile and print out 5 .

Multiple Overloading

We can fairly easily extend our basic_conditional class to handle any number of overloads by using recursion:

template<class F, class... Fs> struct conditional : basic_conditional<F, conditional<Fs...>> {}; template<class F> struct conditional<F> : F {};

So now if we want our advance function to work for negative numbers, we can write it like the following: