问题描述
假设我声明和定义了结构 X
:
struct X {
int i;
X(int i = 0) : i(i) { std::cout << "X(int i = " << i << "): X[" << this << "]" << std::endl; }
int multiply(int a,int b = 1) { return i * a * b; }
};
我想通过函数指针调用 X::multiply
。为此,我定义了一个 proxy
函数:
template <typename ObjectType,typename ReturnType,typename... ParameterTypes>
ReturnType proxy(ObjectType * object,ReturnType (ObjectType::* func)(ParameterTypes...),ParameterTypes... params) {
return (object->*func)(params...);
}
我是这样使用的:
int main() {
X x(10);
int a = 5;
int r = proxy<X,int,int>(&x,&X::multiply,a,2); // proxy<...> works
//int r = proxy(&x,2); // proxy works
//int r = proxy2<X,2); // proxy2<...> does not work
//int r = proxy2(&x,2); // proxy2 works
std::cout << "Proxy-Test:"
<< "\na = " << 5
<< "\nr = " << r
<< std::endl;
}
proxy<...>
甚至 proxy
都像魅力一样工作,除了我不能只用一个参数调用 proxy
虽然第二个参数应该是可选的。 (在 proxy
的调用中省略 right-value-argument 2 会产生错误。)当然,proxy
期望 恰好 两个参数,所以我还创建了proxy
函数的另一个版本:
template <typename ObjectType,typename... ParameterTypes,typename... ArgumentTypes>
ReturnType proxy4(ObjectType * object,ArgumentTypes... args) {
return (object->*func)(args...);
}
这个版本的参数和参数类型被分成单独的参数包。奇怪的是,调用 proxy2<X,2);
使编译器认为此调用的 ParameterTypes 是 (int,int) 而 ArgumentTypes 是 (int,int)。
同样奇怪的是调用 proxy2(&x,2);
工作得很好。
如果我省略了正确的值 2,这些都不起作用。
问题仍然存在:如何仅使用 a 参数调用 X::multiply
,因此不指定 b?
解决方法
默认参数不能用于通过函数指针(pmf 或其他方式)调用,因为默认参数不是函数类型的一部分,因此不是任何函数指针的一部分。
一个不使用模板或 pmf 的简单示例:
bool getFlag();
int foo(int,int);
int bar(int,int=42);
bool flag = getFlag();
auto pf = flag ? foo : bar;
int answer1 = pf(5,6); // ok
int answer2 = pf(5); // ?????
最后一行不能静态类型检查,因为它的有效性取决于 flag
的运行时值。任何语言都有两个可行的选择:(1) 声明该行无条件无效 (2) 在运行时检查它。 C++ 作为静态类型语言选择第一个选项。没有其他办法了。模板或 pmf 不会改变这一事实。
如何仅使用 a 参数调用 X::multiply
,因此不指定 b?
你不能,拦截 X::multiply
的类型及其参数的类型。
问题是函数/方法的默认值不是函数签名的一部分,因此当您传递 X::multiply
指针时,方法的类型是 int (X::*)(int,int)
并且第二个参数是可选的(具有 1
默认值)丢失。
因此,在 proxy()
/proxy2()
中,您不能仅使用参数调用 X::multiply()
,因为编译器知道 X::multiply()
需要 two强> 论据。
正如 Yakk - Adam Nevraumont 所建议的,解决这个问题的常用方法是通过 lambda 函数。这允许直接使用 multiply()
方法,避免它的类型推导并维护可选参数(和默认值)的知识。
例如,如果不需要传递x
对象但可以在lambda内部传递它,则可以编写一个简单的proxy_1()
函数
template <typename Lambda,typename ... ArgumentTypes>
auto proxy_1 (Lambda func,ArgumentTypes ... args)
{ return func(args...); }
可以如下使用
X x{10};
int a{5};
auto l1 { [&](auto... args) { return x.multiply(args...); } };
int r1a { proxy_1(l1,a,2) };
int r1b { proxy_1(l1,a) };
int r1c { proxy_1<decltype(l1),int,int>(l1,2) };
int r1d { proxy_1<decltype(l1),a) };
相反,如果您需要将函数和对象作为单独的参数传递,proxy_2()
会变得稍微复杂一些
template <typename Object,typename Lambda,typename ... ArgumentTypes>
auto proxy_2 (Object obj,Lambda func,ArgumentTypes ... args)
{ return func(obj,args...); }
并且您必须编写的 lambda 将对象作为参数接收
X x{10};
int a{5};
auto l2 { [](auto obj,auto... args) { return obj.multiply(args...); } };
int r2a { proxy_2(x,l2,2) };
int r2b { proxy_2(x,a) };
int r2c { proxy_2<X,decltype(l2),int>(x,2) };
int r2d { proxy_2<X,a) };
以下是一个完整的编译 C++14 示例
#include <iostream>
struct X
{
int i;
X (int i0 = 0) : i{i0}
{ std::cout << "X(int i = " << i << "): X[" << this << "]" << std::endl; }
int multiply (int a,int b = 1)
{ return i * a * b; }
};
template <typename Lambda,ArgumentTypes ... args)
{ return func(args...); }
template <typename Object,args...); }
int main()
{
X x{10};
int a{5};
auto l1 { [&](auto... args) { return x.multiply(args...); } };
int r1a { proxy_1(l1,a) };
auto l2 { [](auto obj,a) };
std::cout << "Proxy-Test:"
<< "\na = " << 5
<< "\nr1a = " << r1a
<< "\nr1b = " << r1b
<< "\nr1c = " << r1c
<< "\nr1d = " << r1d
<< "\nr2a = " << r2a
<< "\nr2b = " << r2b
<< "\nr2c = " << r2c
<< "\nr2d = " << r2d
<< std::endl;
}