0

假设我有这样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-function:

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, int>(&x, &X::multiply, a, 2);         // proxy<...> works
    //int r = proxy(&x, &X::multiply, a, 2);                         // proxy works
    //int r = proxy2<X, int, int, int, int>(&x, &X::multiply, a, 2); // proxy2<...> does not work
    //int r = proxy2(&x, &X::multiply, a, 2);                        // proxy2 works

    std::cout << "Proxy-Test:"
              << "\na = " << 5
              << "\nr = " << r
              << std::endl;
}

proxy<...>甚至proxy像魅力一样工作,除了我不能proxy只用一个参数调用,尽管第二个参数应该是可选的。(在 's 的调用中省略右值参数 2proxy会产生错误。)当然,proxy期望正好有两个参数,所以我还创建了另一个版本的proxy-function:

template <typename ObjectType,
          typename ReturnType,
          typename... ParameterTypes,
          typename... ArgumentTypes>
ReturnType proxy4(ObjectType * object,
                  ReturnType (ObjectType::* func)(ParameterTypes...),
                  ArgumentTypes... args) {
    return (object->*func)(args...);
}

此版本的参数和参数类型分为单独的参数包。奇怪的是,调用proxy2<X, int, int, int, int>(&x, &X::multiply, a, 2);使编译器认为此调用的 ParameterTypes 是 (int, int, int) 而 ArgumentTypes 是 (int, int)。同样奇怪的是调用proxy2(&x, &X::multiply, a, 2);工作得很好。如果我遗漏了正确的值 2,这些都不起作用。

问题仍然存在:如何X::multiply只使用 a 参数而不指定 b 来调用?

4

2 回答 2

1

如何X::multiply只使用 a 参数而不指定 b 来调用?

你不能,拦截X::multiply它的参数的类型和类型。

问题是函数/方法的默认值不是函数签名的一部分,所以当你传递一个X::multiply指针时,方法的类型int (X::*)(int, int)和第二个参数是可选的(具有1默认值)的事实会丢失。

所以,在proxy()/里面proxy2(),你不能X::multiply()只用一个参数调用,因为编译器知道它X::multiply()需要两个参数。

正如 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, a, 2) };
   int r1d { proxy_1<decltype(l1), int>(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, a, 2) };
   int r2b { proxy_2(x, l2, a) };
   int r2c { proxy_2<X, decltype(l2), int, int>(x, l2, a, 2) };
   int r2d { proxy_2<X, decltype(l2), int>(x, l2, 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,
          typename ... ArgumentTypes>
auto proxy_1 (Lambda func,
              ArgumentTypes ... args)
 { return func(args...); }

template <typename Object,
          typename Lambda,
          typename ... ArgumentTypes>
auto proxy_2 (Object obj,
              Lambda func,
              ArgumentTypes ... args)
 { return func(obj, args...); }

int main()
 {
   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, a, 2) };
   int r1d { proxy_1<decltype(l1), int>(l1, a) };

   auto l2 { [](auto obj, auto... args) { return obj.multiply(args...); } };

   int r2a { proxy_2(x, l2, a, 2) };
   int r2b { proxy_2(x, l2, a) };
   int r2c { proxy_2<X, decltype(l2), int, int>(x, l2, a, 2) };
   int r2d { proxy_2<X, decltype(l2), int>(x, l2, 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;
 }
于 2021-07-07T13:38:46.107 回答
1

默认参数不能与通过函数指针(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 不会改变这一事实。

于 2021-07-07T13:25:12.047 回答