Passing overloaded functions to templates

General algorithms like sorting or searching are usually expressed as templates. Since many of them should allow the users to specify how certain things are to be done (for instance comparing two values), they usually take a function-template as argument:

int main() { main() { auto vec = std:: vector< int >{ 3 , 76 , 1 , 400 , 11 }; vec =vector<>{}; // sort by the value modulo 3: std:: sort(vec.begin(), vec.end(), []( int l, int r){ return l% 3 < r% 3 ;}); sort(vec.begin(), vec.end(), [](l,r){l%< r%;}); }

This is all nice and we can even use existing functions:

int main() { main() { const auto vec1 = std:: vector< int >{ 3 , 15 , 7 , 8 , 1 }; vec1 =vector<>{}; const auto vec2 = std:: vector< int >{ 5 , 1 , 3 , 7 , 3 }; vec2 =vector<>{}; auto vec3 = std:: vector< int >{}; vec3 =vector<>{}; std:: transform(vec1.begin(), vec1.end(), vec2.begin(), std:: back_inserter(vec3), std:: max); transform(vec1.begin(), vec1.end(), vec2.begin(),back_inserter(vec3),max); }

That is, if the passed function is not overloaded. The above example will fail harshly:

$ clang++ -Wall -Wextra -Wpedantic -std=c++1y main.cpp main.cpp:12:2: error: no matching function for call to 'transform' std::transform(vec1.begin(), vec1.end(), vec2.begin(), std::back_inserter(vec3)... ^~~~~~~~~~~~~~ /usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_algo.h:4189:5: note: candidate template ignored: couldn't infer template argument '_BinaryOperation' transform(_InputIterator1 __first1, _InputIterator1 __last1, ^ /usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/stl_algo.h:4152:5: note: candidate function template not viable: requires 4 arguments, but 5 were provided transform(_InputIterator __first, _InputIterator __last, ^ 1 error generated.

Now, this is very specific user-code (there are no templates anywhere on the user-side) and we know exactly which overload we want to call. Even the compiler would soon find out, if it actually tried.

The usual solution here is a lambda:

std:: transform(vec1.begin(), vec1.end(), vec2.begin(), std:: back_inserter(vec3), transform(vec1.begin(), vec1.end(), vec2.begin(),back_inserter(vec3), int l, int r){ return std:: max(l, r);}); [](l,r){max(l, r);});

This is ridiculous: In order to pass a normal global function-template we have to write an explicit wrapper-lambda around it that is more than four times longer and doesn’t even use the exactly correct types ( int vs const int& ). It would actually be a bug if we needed to preserve the return-type.

The other usual solutions (passing the template-argument explicitly and using static_cast ) have even more problems, so let’s just ignore them here.

Let’s look at the general case: We get an arbitrary number of arguments of arbitrary types that we want to use to call one function of an overload-set that will return an arbitrary type.

Assuming that the function is called “fun”, the lambda to achieve that heroic act looks like this:

auto &&...args)-> decltype ( auto ){ return fun( std:: forward< decltype (args)>(args)...);} [](&&...args)->){fun(forward(args)...);}

This uses quite a few advanced language-feature that many C++-programmers are probably happy to avoid: Variadic templates, perfect-forwarding, std::forward and decltype(auto) as explicit lambda-return-type. But hey, it is correct! We just needed 83 characters of difficult to understand (for normal people) boilerplate to pass a function whose name is three letters long!

This is embarrassing! To get the behavior that we should get by default, we have to write so much code. Personally I also hate the idea that, as far as I see the situation, we cannot get better by throwing even more templates at the problem (which usually helps quite a lot).

This leaves us with a tool that we should almost always try to avoid: The preprocessor.

As terrifying it is, as much as it is against everything that modern C++ is about, it seems to be the least bad solution in this case:

#define MYLIB_RESOLVE_OVERLOAD (...) \ (...) []( auto &&...args)-> decltype ( auto ){ return __VA_ARGS__ ( std:: forward< decltype (args)>(args)...);} [](&&...args)->){forward(args)...);}

Two notes about this macro:

Originally there were parenthesis around __VA_ARGS__ and I mentioned here, that I was not 100% sure that those should be there and whether this could make some valid code ill-formed. As it was pointed out on reddit, this prevents ADL which is probably not what we want here, so I removed them.

and I mentioned here, that I was not 100% sure that those should be there and whether this could make some valid code ill-formed. As it was pointed out on reddit, this prevents ADL which is probably not what we want here, so I removed them. We really must use __VA_ARGS__ , since the name of the function could include a template with more than one argument (think of static member-function-templates), and those will only work like this (The preprocessor would consider the comma that divides the template-arguments as a comma that separates macro-arguments).

After that we are now able to write our code like this:

int main() { main() { const auto vec1 = std:: vector< int >{ 3 , 15 , 7 , 8 , 1 }; vec1 =vector<>{}; const auto vec2 = std:: vector< int >{ 5 , 1 , 3 , 7 , 3 }; vec2 =vector<>{}; auto vec3 = std:: vector< int >{}; vec3 =vector<>{}; std:: transform(vec1.begin(), vec1.end(), vec2.begin(), std:: back_inserter(vec3), transform(vec1.begin(), vec1.end(), vec2.begin(),back_inserter(vec3), std:: max)); MYLIB_RESOLVE_OVERLOAD(max)); }

Which is certainly better then the other alternatives I’ve seen.