免责声明:这个答案经过了一系列的编辑和更正,非常感谢 Jarod42 的耐心和帮助,以及 cigien 的富有成果的讨论。它比必要的时间长一点,但我觉得保留一点历史是值得的。它是:最简单的/我更喜欢的/对先前混乱的一些解释。如需快速解答,请阅读第二部分。
简单
您可以为函数指针使用auto 模板参数(C++17 起),并从参数中推导出T:
template<auto F, typename T>
void DoStuff(T& s)
{
int x = F(s);
}
int foo(double&){ return 42;}
int main() {
double x;
DoStuff<&foo>(x);
}
Live Demo
“权利”
上面的缺点是F和T是“独立的”。您可以使用例如std::string y; 调用DoStuff<&foo>(y),并且只有在调用F 时实例化才会失败。这可能会导致不必要的复杂错误消息,具体取决于您对F 和s 的实际操作。要在将错误的T 传递给DoStuff<F> 时已经在调用站点触发错误,您可以使用特征来推断F 的参数类型并直接将其用作DoStuff 的参数类型:
template <typename T> struct param_type;
template <typename R,typename P>
struct param_type< R(*)(P&)> {
using type = P;
};
template<auto F>
void DoStuff(typename param_type<decltype(F)>::type& s)
{
int x = F(s); // (1)
}
int foo(double&){ return 42;}
int main() {
double x;
DoStuff<foo>(x);
std::string y;
DoStuff<foo>(y); // (2) error
}
现在,之前只会发生在模板 (1) 中的错误已经发生在 main (2) 中,并且错误消息更加清晰。
Live Demo
“错”
主要出于好奇,考虑这种从函数指针推导参数类型的方式:
template <typename T> struct param_type;
template <typename R,typename P>
struct param_type< R(*)(P&)> {
using type = P;
};
template<auto F, typename T = typename param_type<decltype(F)>::type>
void DoStuffX(T& s)
{
}
int foo(double&){ return 42;}
int main() {
double x;
DoStuffX<foo>(x);
}
这是我最初的答案,但它实际上并没有像我想象的那样做。请注意,我实际上并没有在DoStuff 中调用F,令我惊讶的是,它编译了:
int main() {
std::string x;
DoStuffX<foo>(x);
}
原因是当T 可以从传递的参数中推断出来时,没有使用默认模板参数(参见here)。也就是说,DoStuffX<foo>(x); 实际上实例化了DoStuffX<foo,std::string>。我们仍然可以通过以下方式获得默认值:
int main() {
std::string x;
auto f_ptr = &DoStuffX<foo>;
f_ptr(x); // error
}
现在用std::string调用DoStuffX<foo>是编译器错误,因为这里DoStuffX<foo>被实例化为DoStuffX<foo,double>,使用了默认参数(没有参数可以用来推断T时DoStuffX 已实例化)。