Passing a templated function as method argument without lambdas?

Viewed 387

I did like to being able to use extFunction or std::max or std::min as argument for the square method without declaring a lambda :

template<typename T>
T extFunction(T a, T b)
{
    return a;
}

class Stuff
{
public:
    template <typename F>
    int square(int num, int num2, F&& func) 
    {
        return func(num, num2);
    }
};

int main()
{
    Stuff s;
    std::cout << s.square(1, 2, std::max<int>) << std::endl;
    return 0;
}

But the compiler (gcc 11.1) is telling me that:

the function is ambiguous : "couldn't deduce template parameter 'F'"

Is there a simple way to do that without lambdas ?

EDIT:

maybe it would be interesting to show how to do this with lambdas :

std::cout << s.square(1,2,[](auto&& a, auto&& b){return std::max(a,b);}) << std::endl;
    
std::cout << s.square(1,2,[](auto&& a, auto&& b){return std::min(a,b);}) << std::endl;
    
std::cout << s.square(1,2,[](auto&& a, auto&& b){return extFunction(a,b);}) << std::endl;

Output :

Program returned: 0
Program stdout

2
1
1
3 Answers

The problem with std::max and std::min is that they are ambiguous as you can see here. There are several overloads and you would have to tell your compiler which one you would like to use.


  • In modern C++ you would actually wrap the wished overload into a lambda or functor.

  • If for some reason you are stuck with legacy code (e.g. C++03) where you can't use a lambda you could perform a static_cast to tell the compiler which overload to use as follows

    template <typename F, typename... Args>
    auto func(F f, Args... args) {
      return f(args...);
    }
    
    int main() {
      std::cout << func(static_cast<double const&(*)(double const&, double const&)>(std::max), 3.0, 2.0) << std::endl;
      return EXIT_SUCCESS;
    }
    

    Try it here!

    As already mentioned by user JeJo this is considered though bad practice (since C++20), potentially resulting in unspecified (not undefined!) behaviour, as std::min and std::max are not addressable. The part of the standard he is referring to was only introduced recently in C++20 based on this draft. Another post by user Barry about this can be found here.

In C++, when you want to pass an invocable, which is polymorphic in the sense that it has multiple potential signatures, the best way to do it is to wrap it in an object. Overload sets are not a first class citizen. There are proposals though.

When the invocable is something you are providing to the users, it is straightforward to define it as an object:

inline constexpr auto extFunction = []<typename T>(T a, T b) -> T {
     return a;
};

When you have an overload set, you must wrap it. That is annoying but you can reduce the boilerplate with a macro:

#define FWD(x) static_cast<decltype(x)&&>(x)
#define RETURNS(expr) noexcept(noexcept(expr)) -> decltype(expr) { return expr; }
#define OVERLOADS_OF(name) [&](auto&& ...args) RETURNS(name(FWD(args)...))

Your examples would look like:

std::cout << s.square(1, 2, OVERLOADS_OF(std::max)) << std::endl;        
std::cout << s.square(1, 2, OVERLOADS_OF(std::min)) << std::endl;
std::cout << s.square(1, 2, extFunction) << std::endl;

For completeness, under fairly narrow circumstances, the receiver could also define function pointer overloads:

int square(int num, int num2, int (*func)(int, int));
s.square(1, 2, extFunction); // now works

Template deduction works from a function pointer, so the template parameter of extFunction is deduced.

As mentioned in other answers, for the standard library, you want to avoid relying on particularities of most standard library functions, because the standard only promises you can invoke them and does not guarantee their form.

Related