函数重载作为模板函数的参数

huangapple go评论88阅读模式
英文:

Overloaded Function as Argument to Templated Function

问题

我遇到了一个关于clang++中模板的问题,我不知道如何解决。

我希望使用模板函数,它接受一个类和一个函数作为参数。但是当函数是一个重载函数时,我会得到错误消息candidate template ignored: couldn't infer template argument 'F',这是完全合理的。

例如,下面的程序示例:

#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;
}

正按照我的期望工作。现在,如果我使用重载函数,比如sin而不是sinf,则无法推断出模板参数。我该如何更改foo的函数模板以帮助编译器解析F的类型?

英文:

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?

答案1

得分: 6

你可以使用lambda来解决函数重载的歧义,例如:

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

另外,值得一提的是,在C++中,你应该使用<cmath> 而不是直接使用 <math.h>。如果不想在调用时加上命名空间限定符,你可以使用 using namespace std; 或更好地使用 using std::sin; 来将 std::sin 引入当前的命名空间。

英文:

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 &lt;cmath&gt; instead of &lt;math.h&gt; 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.

答案2

得分: 5

有多种方法可以解决这个问题。首先,你可以将其转换为适当的函数指针类型:

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

然而,这是未指定的行为(可能是不合法的),因为 sin 不是一个可寻址的函数
这对于你自己的函数来说是完全可以的,但不适用于标准库中的函数。

为了安全起见,你可以将这个函数封装在lambda表达式中:

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

关于频繁“提升”函数的注意事项

经常需要像这样“提升”标准库函数,所以有些人定义了一个宏:

// TODO: 完美转发,条件性 noexcept,...
#define LIFT(...) [](auto &&...args) { return __VA_ARGS__(args...); }

foo(LIFT(sin), 0.34);

关于减少实例化的注意事项

[](double x) { return sin(x); } 表示一个独特的 闭包类型,因此我们调用 foo<CLOSURE, double>。我们可能仍然希望调用 foo<double(*)(double), double>,以节省实例化。

如果 lambda 表达式没有捕获,它定义的 闭包类型 隐式转换为函数指针。你可以显式转换为 double(*)(double),或者如果你想要使用函数指针而不是 lambda 表达式来调用 foo,则可以传递 +[](double x) ...

关于完美转发的注意事项

你的原始 foo 可以通过以下方式改进:

template<class T, class F>
T foo(F &&f, T &&x){
  return std::forward<F>(f)(std::forward<T>(x));
}
  • 转发 x 意味着我们在调用 f 时可能使用移动构造函数
  • 转发 f 意味着我们可能使用右值引用限定的调用运算符(这种情况很少见,但在理论上是可能的)
英文:

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

foo(static_cast&lt;double(*)(double)&gt;(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 &amp;&amp;...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&lt;CLOSURE, double&gt;. We might still want to call foo&lt;double(*)(double), double&gt; 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&lt;class T, class F&gt;
T foo(F &amp;&amp;f, T &amp;&amp;x){
  return std::forward&lt;F&gt;(f)(std::forward&lt;T&gt;(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)

huangapple
  • 本文由 发表于 2023年6月29日 01:26:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76575455.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定