4

I have stumbled upon an issue with templates in clang++ that I do not know how to solve.

I wish to use templated functions that take a class and a function as arguments. But when the function is an overloaded function, I get the error candidate template ignored: couldn't infer template argument 'F' which is totally sensible.

For instance, the example program

#include <iostream>
#include <math.h>

template<class T, typename F>
T foo(F f,T x){
  return f(x);
}

int main(void) {
  std::cout << "foo(sinf,0.34) is " << foo(sinf,0.34) << std::endl;
  return 0;
}

works exactly as I want. Now, if I use an overloaded function such as sin instead of sinf, the template argument cannot be inferred. How can I change the function template of foo to help the compiler resolving the type of F?

  • Oh no, I think I accidentally deleted the comments. I am very sorry! – Anton Rydahl Jun 28 '23 at 17:48
  • Your program has unspecified behaviour because `sinf` is not an addresable function, [see](https://stackoverflow.com/questions/55687044/can-i-take-the-address-of-a-function-defined-in-standard-library). You need to wrap it in your own function or lambda. So might just as well wrap `sin`. – n. m. could be an AI Jun 28 '23 at 17:51

2 Answers2

6

You can use a lambda to solve the ambiguity with overloads, eg:

foo([](double d){ return sin(d); }, 0.34)

On a side note, in C++, you should use <cmath> instead of <math.h> directly. You can use using namespace std; or better using std::sin; to bring std::sin into the calling namespace if you don't want to qualify it at the call site.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I tried using `cmath` and and adding the standard namespace, but that did not help. But the lambda function does the job. Thanks a bunch! – Anton Rydahl Jun 28 '23 at 17:54
  • @AntonRydahl `` defines only 1 `sin()` function, in the global namespace. `` defines overloaded `sin()` functions, in the `std` namespace. – Remy Lebeau Jun 28 '23 at 18:01
  • `cmath` is allowed to define the global function, and `math.h` is allowed to define the one in `std`. – HolyBlackCat Jun 28 '23 at 18:02
  • 1
    Ideally you would always `#include ` and use `std::sin`, anything beyond that is relying on features that aren't guaranteed in C++. – Jan Schultke Jun 28 '23 at 18:05
  • Just to be clear: `` definitely declares `::sin` and may declare `std::sin`. `` definitely declares `std::sin` and may declare `::sin`. – Jerry Coffin Jun 28 '23 at 18:11
5

There are multiple ways to resolve this. First of all, you could convert to the appropriate function pointer type:

foo(static_cast<double(*)(double)>(sin));

However, this is unspecified behavior (possibly ill-formed), because sin is not an addressable function. It's perfectly fine to do this for your own functions, just not for functions in the standard library.

To be safe, you can wrap this function in a lambda expression:

foo([](double x) { return sin(x); }, 0.34);

Note on "lifting" functions frequently

It happens quite often that you have to "lift" standard library functions like this, so some people define a macro:

// TODO: perfect forwarding, conditional noexcept, ...
#define LIFT(...) [](auto &&...args) { return __VA_ARGS__(args...); }

foo(LIFT(sin), 0.34);

Note on reducing instantiations

[](double x) { return sin(x); } denotes a unique closure type, so we are calling foo<CLOSURE, double>. We might still want to call foo<double(*)(double), double> instead, to save on instatiations.

The closure type defined by a lambda expression is implicitly convertible to a function pointer if the lambda expression has no captures. You could explicitly cast to a double(*)(double), or pass +[](double x) ... if you wanted to call foo with a function pointer instead of the lambda expression.

Note on perfect forwarding

Your original foo could be improved as follows:

template<class T, class F>
T foo(F &&f, T &&x){
  return std::forward<F>(f)(std::forward<T>(x));
}
  • forwarding x means that we possibly use the move constructor when calling f
  • forwarding f means that we possibly use an rvalue-ref-qualified call operator (it's rare that this exists, but it theoretically can)
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96