仅使用必要的参数调用函数指针

问题描述

假设我声明和定义了结构 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;
 }