8

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
  • 4
    [`std::max`](https://en.cppreference.com/w/cpp/algorithm/max)`` has several overloads, `const int&(*)(const int&, const int&)` and `int (*) (std::initializer_list)`. – Jarod42 Jul 28 '21 at 08:03
  • Why do you want to do this? Is it curiosity or an unspoken requirement? – Jeff Garrett Jul 28 '21 at 23:04
  • @JeffGarrett I have two algorithms to execute which are the same except one is needing the max of a set of values and the other one is needing the min. Obviously I don't want to write the algorithm twice as the difference is only the call of one function or the other. – fabien le corre Aug 10 '21 at 20:44

3 Answers3

10

You should not be taking the address of a standard library function. See in detail here:

Can I take the address of a function defined in standard library?

Therefore, there is no simple way other than, pack std::max into a function object(i.e. lambda, functor) or in a function.

JeJo
  • 30,635
  • 6
  • 49
  • 88
2

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.

2b-t
  • 2,414
  • 1
  • 10
  • 18
  • 3
    As you can read in link from other answer, your solution would be UB for (non-allowed) `std` functions. – Jarod42 Jul 28 '21 at 08:36
  • 1
    @Jarod42 This part of the standard was only introduced in C++20 following [this draft](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0551r3.pdf) and the behaviour is only unspecified and not undefined. I added a corresponding remark. – 2b-t Jul 28 '21 at 08:44
0

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.

Jeff Garrett
  • 5,863
  • 1
  • 13
  • 12