Settings

Settings other than “Default” will be saved in local storage to persist across pages, reloads, and sessions.

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:

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

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

C++ main.cpp Download
int main() {const auto vec1 = std::vector<int>{3,15,7,8,1};const auto vec2 = std::vector<int>{5,1,3,7,3};auto vec3 = std::vector<int>{};std::transform(vec1.begin(), vec1.end(), vec2.begin(), std::back_inserter(vec3), std::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/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/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), [](int l, int r){return std::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: 1

[](auto&&...args)->decltype(auto){return fun(std::forward<decltype(args)>(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,2 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:

C++ resolve_overload.hpp Download
#define MYLIB_RESOLVE_OVERLOAD(...) [](auto&&...args) -> decltype(auto){ return __VA_ARGS__(std::forward<decltype(args)>(args)...); }

Two notes about this macro:

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

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

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