英文:
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 <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.
答案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<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 callingf
- forwarding
f
means that we possibly use an rvalue-ref-qualified call operator (it's rare that this exists, but it theoretically can)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论