在以函数为参数的模板函数中提取输入参数的类型
Extract type of input parameter in templated function taking a function as parameter
我有这个功能:
template<typename T, int (*F)(T&)>
void DoStuff(T& s)
{
auto an = make_any<T>(s);
cout << _GetDataFromAny<MyStruct, F>(an);
}
需要这样调用:
DoStuff<MyStruct, Fun>(s);
这很好用,但我不喜欢必须指定第一种类型,因为它是第二个参数签名的一部分。我希望能够推断出它,这样我就可以调用:
DoStuff<Fun>(s);
但是,我不知道如何在模板中指定需要从函数F的签名中推导出类型T。
这可能吗?
您可以编写一个帮助程序来推断函数指针的参数类型 returns int
:
template<typename T>
T arg_type(int(*)(T&));
然后稍微重写你的函数模板,将函数指针作为非类型模板参数,并从中找出参数类型
template<auto F, typename T = decltype(arg_type(F))>
void DoStuff(T& s) {
// ...
}
免责声明:这个答案经过了一系列的编辑和更正,非常感谢 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);
}
“对”
上面的缺点是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) 中,并且错误消息更加清晰。
“错误”
主要是好奇,考虑这种从函数指针推导出参数类型的方式:
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>
,使用默认参数(没有参数实例化 DoStuffX
时可用于推断 T
)。
我有这个功能:
template<typename T, int (*F)(T&)>
void DoStuff(T& s)
{
auto an = make_any<T>(s);
cout << _GetDataFromAny<MyStruct, F>(an);
}
需要这样调用:
DoStuff<MyStruct, Fun>(s);
这很好用,但我不喜欢必须指定第一种类型,因为它是第二个参数签名的一部分。我希望能够推断出它,这样我就可以调用:
DoStuff<Fun>(s);
但是,我不知道如何在模板中指定需要从函数F的签名中推导出类型T。
这可能吗?
您可以编写一个帮助程序来推断函数指针的参数类型 returns int
:
template<typename T>
T arg_type(int(*)(T&));
然后稍微重写你的函数模板,将函数指针作为非类型模板参数,并从中找出参数类型
template<auto F, typename T = decltype(arg_type(F))>
void DoStuff(T& s) {
// ...
}
免责声明:这个答案经过了一系列的编辑和更正,非常感谢 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);
}
“对”
上面的缺点是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) 中,并且错误消息更加清晰。
“错误”
主要是好奇,考虑这种从函数指针推导出参数类型的方式:
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>
,使用默认参数(没有参数实例化 DoStuffX
时可用于推断 T
)。