如何编写具有可变参数的成员函数作为模板参数
How to write a member function with variadic parameters as template parameter
是否可以像下面这样用 C++14 编写模板函数
这是示例https://godbolt.org/z/9gRk-t
// pseudo code
#include <type_traits>
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args)
{
return (obj.*mf)(std::forward<Args>(args)...);
}
所以,为了测试 class
struct Test
{
int Func(){return 1;};
bool Func(bool){return true;}; // overload
void FuncInt(int){};
};
模板可以用于下面的用例(但失败了)
int main()
{
Test test;
// for overload case
auto a = Call<int()>(test, &Test::Func);
auto b = Call<bool(bool)>(test, &Test::Func, true);
// for non-overload case
Call(test, &Test::FuncInt, 1);
return 0;
}
这里是错误信息。
#1 with x64 msvc v19.24
example.cpp
<source>(23): error C2672: 'Call': no matching overloaded function found
<source>(23): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
<source>(24): error C2672: 'Call': no matching overloaded function found
<source>(24): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
Compiler returned: 2
我想通了。静静的喜欢std::men_fn.
这是下面的代码和示例https://godbolt.org/z/NoWPV_
#include <type_traits>
template<typename T>
struct Proxy
{
template<typename R, typename ...Args>
decltype(auto) Call(T& obj, R T::*mf, Args&&... args)
{
return (obj.*mf)(std::forward<Args>(args)...);
}
};
struct Test
{
int Func(){return 1;};
bool Func(bool){return true;}; // overload
void FuncInt(int){};
};
int main()
{
Test test;
Proxy<Test> proxy;
// for overload case
auto a = proxy.Call<int()>(test, &Test::Func);
auto b = proxy.Call<bool(bool)>(test, &Test::Func, true);
// for non-overload case
proxy.Call(test, &Test::FuncInt, 1);
return 0;
}
问题
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args)
就是Args
推导了两次,应该是一样的
有几种方法可以解决该问题:
添加额外的模板参数:
template <typename T, typename R, typename... Args, typename ... Ts>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Ts&&... args)
{
return (obj.*mf)(std::forward<Ts>(args)...);
}
或使参数不可推导(我使用 C++20 中的 std::type_identity
,但可以在以前的版本中简单地重新实现):
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), std::type_identity_t<Args>... args)
{
return (obj.*mf)(std::forward<Args>(args)...);
}
或完全更改签名:
template <typename T, typename M, typename... Args>
decltype(auto) Call(T& obj, M mf, Args&&... args)
{
return (obj.*mf)(std::forward<Args>(args)...);
}
在您的 Call
声明中:
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args);
函数模板采用(或可能尝试推断)两个或更多模板参数:第一个是 T
,第二个是 R
,其余是 Args
。因此,在 Call<int()>
和 Call<bool(bool)>
中将单个函数类型作为第一个模板参数是错误的。正确的调用方式是
auto a = Call<Test, int>(test, &Test::Func);
auto b = Call<Test, bool, bool>(test, &Test::Func, true);
另一个问题是,如果你想推导模板参数,就像在非重载的情况下一样,因为 Args
包出现了两次,它只有在列表从成员函数和从尾随参数完全相同:
int n = 3;
Call(test, &Test::FuncInt, n); // error!
// Args... is deduced to `int` from `&Test::FuncInt`, but deduced to `int&`
// from `n` since it's an lvalue matching a forwarding reference parameter.
如果你更喜欢函数类型语法,你可以使用:
的解决方案
template <typename FuncT, typename T, typename... Args>
constexpr decltype(auto) Call(T& obj, FuncT T::*mf, Args&&... args)
noexcept(noexcept((obj.*mf)(std::forward<Args>(args)...)))
{
return (obj.*mf)(std::forward<Args>(args)...);
}
// main() exactly as in question, including Call<int()> and Call<bool(bool)>.
FuncT T::*mf
是声明指向成员的指针的语法,通常用于指向数据成员,但如果 FuncT
类型为一个函数类型。 (我添加了 constexpr
和条件异常说明符以使其更通用。)
这也解决了原始问题,它不能用于调用 const
或具有 ref 限定符的成员函数,因为这会创建不同的函数类型:
class Test2 {
public:
int get() const;
void set() &;
};
void driver_Test2() {
Test2 test;
// Error with original Call:
// Type of &Test2::get is "int (Test2::*)() const",
// which cannot match "R (Test2::*)(Args...)"
int a = Call(test, &Test2::get);
// Error with original Call:
// Type of &Test2::set is "void (Test2::*)(int) &",
// which cannot match "R (Test2::*)(Args...)"
Call(test, &Test2::set, 1);
}
但是对于新的 Call
定义,driver_Test2
很好,因为任何非静态成员函数都可以匹配 FuncT T::*
。如果我们想为 driver_Test2
中的调用提供一个模板参数,可能是因为成员函数被重载了,那看起来像 Call<int() const>
和 Call<void() &>
.
是否可以像下面这样用 C++14 编写模板函数
这是示例https://godbolt.org/z/9gRk-t
// pseudo code
#include <type_traits>
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args)
{
return (obj.*mf)(std::forward<Args>(args)...);
}
所以,为了测试 class
struct Test
{
int Func(){return 1;};
bool Func(bool){return true;}; // overload
void FuncInt(int){};
};
模板可以用于下面的用例(但失败了)
int main()
{
Test test;
// for overload case
auto a = Call<int()>(test, &Test::Func);
auto b = Call<bool(bool)>(test, &Test::Func, true);
// for non-overload case
Call(test, &Test::FuncInt, 1);
return 0;
}
这里是错误信息。
#1 with x64 msvc v19.24
example.cpp
<source>(23): error C2672: 'Call': no matching overloaded function found
<source>(23): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
<source>(24): error C2672: 'Call': no matching overloaded function found
<source>(24): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
Compiler returned: 2
我想通了。静静的喜欢std::men_fn.
这是下面的代码和示例https://godbolt.org/z/NoWPV_
#include <type_traits>
template<typename T>
struct Proxy
{
template<typename R, typename ...Args>
decltype(auto) Call(T& obj, R T::*mf, Args&&... args)
{
return (obj.*mf)(std::forward<Args>(args)...);
}
};
struct Test
{
int Func(){return 1;};
bool Func(bool){return true;}; // overload
void FuncInt(int){};
};
int main()
{
Test test;
Proxy<Test> proxy;
// for overload case
auto a = proxy.Call<int()>(test, &Test::Func);
auto b = proxy.Call<bool(bool)>(test, &Test::Func, true);
// for non-overload case
proxy.Call(test, &Test::FuncInt, 1);
return 0;
}
问题
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args)
就是Args
推导了两次,应该是一样的
有几种方法可以解决该问题:
添加额外的模板参数:
template <typename T, typename R, typename... Args, typename ... Ts> decltype(auto) Call(T& obj, R(T::*mf)(Args...), Ts&&... args) { return (obj.*mf)(std::forward<Ts>(args)...); }
或使参数不可推导(我使用 C++20 中的
std::type_identity
,但可以在以前的版本中简单地重新实现):template <typename T, typename R, typename... Args> decltype(auto) Call(T& obj, R(T::*mf)(Args...), std::type_identity_t<Args>... args) { return (obj.*mf)(std::forward<Args>(args)...); }
或完全更改签名:
template <typename T, typename M, typename... Args> decltype(auto) Call(T& obj, M mf, Args&&... args) { return (obj.*mf)(std::forward<Args>(args)...); }
在您的 Call
声明中:
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args);
函数模板采用(或可能尝试推断)两个或更多模板参数:第一个是 T
,第二个是 R
,其余是 Args
。因此,在 Call<int()>
和 Call<bool(bool)>
中将单个函数类型作为第一个模板参数是错误的。正确的调用方式是
auto a = Call<Test, int>(test, &Test::Func);
auto b = Call<Test, bool, bool>(test, &Test::Func, true);
另一个问题是,如果你想推导模板参数,就像在非重载的情况下一样,因为 Args
包出现了两次,它只有在列表从成员函数和从尾随参数完全相同:
int n = 3;
Call(test, &Test::FuncInt, n); // error!
// Args... is deduced to `int` from `&Test::FuncInt`, but deduced to `int&`
// from `n` since it's an lvalue matching a forwarding reference parameter.
如果你更喜欢函数类型语法,你可以使用
template <typename FuncT, typename T, typename... Args>
constexpr decltype(auto) Call(T& obj, FuncT T::*mf, Args&&... args)
noexcept(noexcept((obj.*mf)(std::forward<Args>(args)...)))
{
return (obj.*mf)(std::forward<Args>(args)...);
}
// main() exactly as in question, including Call<int()> and Call<bool(bool)>.
FuncT T::*mf
是声明指向成员的指针的语法,通常用于指向数据成员,但如果 FuncT
类型为一个函数类型。 (我添加了 constexpr
和条件异常说明符以使其更通用。)
这也解决了原始问题,它不能用于调用 const
或具有 ref 限定符的成员函数,因为这会创建不同的函数类型:
class Test2 {
public:
int get() const;
void set() &;
};
void driver_Test2() {
Test2 test;
// Error with original Call:
// Type of &Test2::get is "int (Test2::*)() const",
// which cannot match "R (Test2::*)(Args...)"
int a = Call(test, &Test2::get);
// Error with original Call:
// Type of &Test2::set is "void (Test2::*)(int) &",
// which cannot match "R (Test2::*)(Args...)"
Call(test, &Test2::set, 1);
}
但是对于新的 Call
定义,driver_Test2
很好,因为任何非静态成员函数都可以匹配 FuncT T::*
。如果我们想为 driver_Test2
中的调用提供一个模板参数,可能是因为成员函数被重载了,那看起来像 Call<int() const>
和 Call<void() &>
.