Picture a library. QuantLib. Imagine it has an overloaded function

void f(unsigned int a) {} void f(unsigned int a, unsigned int b) {}

which is used like this in client code

f(0); f(0,0);

Now you want to extend the function adding an additional parameter. For backward compatibility you give it a default value.

void f(unsigned int a, double t = 0.0) {} void f(unsigned int a, unsigned int b, double t = 0.0) {}

Compiling the client code above results in a compiler complaint, g++ for example says

testoverload.cpp:6:10: error: call of overloaded ‘f(int, int)’ is ambiguous f(0,0); ^ testoverload.cpp:1:6: note: candidate: void f(unsigned int, double) void f(unsigned int a, double t = 0.0) {} ^ testoverload.cpp:2:6: note: candidate: void f(unsigned int, unsigned int, double) void f(unsigned int a, unsigned int b, double t = 0.0) {} ^

This is because the arguments `0`

are `int`

‘s, so not directly matching any signature *exactly*. Therefore the compiler tries to convert the arguments to match one of the signatures. However `int`

can both be converted to `unsigned int`

and `double`

, which causes the ambiguity. One can resolve the error by changing the call to

f(0,0.0);

or

f(0u,0u);

or even

f(0.0,0.0)

or

f(0.0,0u)

all of which have one signature strictly better matching than the other, since only requiring conversion of at most *one* (instead of both) of the arguments while the other is already exactly matching.

Does that help ? No, since the new code should work with existing client code out of the box. That is the whole point of backward compatibility.

What we can do is to apply the SFINAE technique I wrote about in an earlier post. We make the second parameter a template parameter

template<class T> void f(unsigned int a, T t = T(0.0)) {} template<class T> void f(unsigned int a, T b, double t = 0.0) {}

and then use meta programming to switch the function definition on and off depending on the type `T`

#include <boost/utility/enable_if.hpp> #include <boost/type_traits/is_float.hpp> #include <boost/type_traits/is_integral.hpp> template<class T> void f(unsigned int a, T t = T(0.0), typename boost::enable_if< boost::is_float<T> >::type* = 0 ) {} template<class T> void f(unsigned int a, T b, double t = 0.0, typename boost::enable_if< boost::is_integral<T> >::type* = 0) {}

Remember that `boost::enable_if<b>::type`

is either `void`

(when `b`

is `true`

, i.e. when `T`

is a float or integral type respectively) or *nothing*. If it is *nothing* it causes a potential compiling error.

However since we are in the process of trying template substitutions here, the whole function definition is just silently discarded from the set of possible template incarnations (“substitution failure is not an error”, SFINAE). What is left is the one function definition we actually want depending on `T`

.

The code then safely chooses the upper definition if we pass a float type like `double`

or `float`

as the second parameter and the lower one for an integral type like `int`

, `unsigned int`

, `std::size_t`

and so on.

Quite heavy machinery to solve an easy problem.

Type inference for the win! Every look into it?

https://en.wikipedia.org/wiki/Type_inference

Looks like C++ also has it…

LikeLike

This is a clever use of templates, but it does not feel right, and is awful to read.

It would be much simpler to just use a different function name.

LikeLike

Totally agree – however a different function name would render the user interface rather inhomogeneous. Moreover this is just not possible in the case of constructors (which was the actual case I ran into).

LikeLike