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() {
	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:

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/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),
               [](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:

#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:

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.


  1. Yes, this really works for all types, including void: In C++ it is legal to return the “result” of a function that returns void if our function returns void too. This is not a problem at all but actually really awesome.↩︎

  2. To answer any “if you want something in the language, write a proposal”: I am a student and don’t have the money to visit standardization meetings nor the experience to write down a feature like this without help from someone experienced. If you find me someone who would mentor me and champion the proposal for the committee, I would very seriously consider writing it.↩︎