C++11 可变参数模板在 class 中调用函数

C++11 variadic templates calling a function inside a class

我正在学习 C++11 中的可变参数模板。如何调用 test.finder 作为 test.var_finder 的函数参数?

#include <functional>
#include <iostream>

class c_test {
    public:

        double finder(double a, double b = 0) {
            return a + b;
        };


        template<typename... Args>
        double var_finder(double c, Args... args, std::function<double (Args... args)> func) {
            return c * func(args...);
        };
};

int main () {
    c_test test;

    std::cout << test.var_finder(0.1, 2, test.finder) << std::endl;

    return 0;
}

我的预期结果是 0.1 * (2 + 0) = 0.2。

这里有很多事情要处理。

首先,把参数包args放在最后,否则编译器不知道在哪里停止匹配参数(除非你指定模板参数)。

template<typename... Args>
double var_finder(double c, std::function<double (Args... args)> func, Args... args)

其次,不要在std::function中命名参数;也不要使用 std::function,而是使用函数指针。

template<typename... Args>
double var_finder(double c, double (*func)(Args...), Args... args)

第三,使 finder 静态,否则你 运行 成成员函数指针,语法更难掌握(稍后详细介绍)。

static double finder(double a, double b = 0)

第三,为 finder 提供可选参数。否则无法推导出参数包,因为它不匹配 funcargs.

std::cout << test.var_finder(0.1, c_test::finder, 2.0, 0.0) << std::endl;

这为您提供了以下信息。

class c_test {
public:
    static double finder(double a, double b = 0) {
        return a + b;
    }

    template<typename... Args>
    double var_finder(double c, double (*func)(Args...), Args... args) {
        return c * func(args...);
    }
};

int main () {
    c_test test;

    std::cout << test.var_finder(0.1, c_test::finder, 2.0, 0.0) << std::endl;
}

Demo

如果你真的需要finder不是静态的,那么你必须处理成员函数指针语法。

首先参数func必须是成员函数指针

double (c_test::*func)(Args...) // parameter declaration
&c_test::finder // argument in call

然后,您需要将 c_test 变量传递给 var_finder 才能调用 func

template<typename... Args>
double var_finder(double c, double (c_test::*func)(Args...), c_test test, Args... args) {
    return c * (test.*func)(args...);
}
// [...]
std::cout << test.var_finder(0.1, &c_test::finder, test, 2.0, 0.0) << std::endl;

这将为您提供以下代码。

class c_test {
public:
    double finder(double a, double b = 0) {
        return a + b;
    }

    template<typename... Args>
    double var_finder(double c, double (c_test::*func)(Args...), c_test test, Args... args) {
        return c * (test.*func)(args...);
    }
};

int main () {
    c_test test;

    std::cout << test.var_finder(0.1, &c_test::finder, test, 2.0, 0.0) << std::endl;
}

Demo

如果您希望能够在不指定可选参数的情况下调用 finder,那么您需要一些中介,例如 lambda。

你的解决方案有很多问题:

1) 不能将成员函数转换为 std::function,因为没有对象就永远无法调用成员函数。您可以通过使成员函数 static 或使用 std::bind 或使用 lambda 来解决此问题。

2) 方法参数中间不能有可变参数列表。可变参数必须到达参数列表的末尾。

3) 您不能在参数列表本身中推导模板参数 "on the fly"。解决方案:使用带有默认模板参数的新模板参数。

4) 我认为没有机会为我们想要在 std::function 中使用的函数设置默认参数,并产生不同的签名 double(double)double(double,double)

class c_test {
    public:

        static double finders(double a, double b = 0) {
            return a + b;
        };

        double finder(double a, double b = 0) {
            return a + b;
        };


        template<typename... Args, typename F= std::function<double(Args...)> >
            double var_finder(double c, F func, Args... args) {
                return c * func(args...);
            }
};

int main () {
    c_test test;

    // use static member
    std::cout << test.var_finder(0.1, test.finders,  2.,3.) << std::endl;

    // use lambda instead of all the hacky things
    std::cout << test.var_finder(0.1, [&test](double p1,double p2){ return test.finder(p1,p2); },  2.,3.) << std::endl;

    return 0;
}

您可以简单地这样做:

class c_test 
{
public:

   double finder(double a, double b = 0) 
   {
      return a + b;
   };


   template<typename Func, typename... Args>
   double var_finder(double c, Func func, Args... args) 
   {
      return c * func(args...);
   };
};

int main() 
{
   c_test test;
   auto f_1 = std::bind(std::mem_fn(&c_test::finder), test, std::placeholders::_1, 0);
   auto f_2 = std::bind(std::mem_fn(&c_test::finder), test, std::placeholders::_1, std::placeholders::_2);

   std::cout << test.var_finder(0.1, f_1, 2.0) << std::endl;
   std::cout << test.var_finder(0.1, f_2, 2.0, 3.0) << std::endl;

   return 0;
}

结果是:

0.2
0.5

我猜你有点把可变参数模板部分和一些设计缺陷混在一起了

开始吧。

Preamble:可变参数模板的正确处理方式是使用rvalue-referencestd::forward实现perfect forwarding.

1) 简单的方法:你根本不需要class

您实际上并没有指代任何成员,因此 class 只会带来复杂性。这些情况最好参考一个免费的函数

#include <functional>
#include <iostream>

double finder(double a, double b = 0) {
    return a + b;
};

template<typename Func, typename... Args>
double var_finder(double c, Func&& f, Args&&... args) {
    return c * std::forward<Func>(f)(std::forward<Args>(args)...);
};

int main () {
    std::cout << var_finder(0.1, finder, 2, 0) << std::endl;

    return 0;
}

Demo 1

您的函数接受 2 个参数,因此您必须将零作为第二个参数传递。

2) 使用 class

你尝试的问题是你想用 c_test 本身的函数调用 c_test.var_finder。现在你必须弄清楚你想要什么样的设计。你可以做出2个假设。首先是 "I want anyway a finder function inside my class",然后你必须使它成为 static 因为它真的不使用 class 成员所以你不需要 c_test 的实例,对吗?所以使用自由函数或静态成员函数会留下 var_finder 实现,你只需要这样调用它

#include <functional>
#include <iostream>

class c_test {
    public:
        static double finder(double a, double b = 0) {
            return a + b;
        };


        template<typename Func, typename... Args>
        double var_finder(double c, Func&& f, Args&&... args) {
            return c * std::forward<Func>(f)(std::forward<Args>(args)...);
        };
};

int main () {
    c_test test;

    std::cout << test.var_finder(0.1, &c_test::finder, 2, 0) << std::endl;

    return 0;
}

Demo 2

您可以做的第二个假设是"nope, I want any function member to be called with var_finder regardless where it comes from"。我强烈反对这种方法,因为它是从糟糕的设计中提取解决方案,所以我建议重新考虑您的设计以落入解决方案 1 或 2。

3) 奖励:设计不错

您可以添加一个非可变参数函数并将用法委托给 lambda 的使用,这允许您在其中使用一个成员函数而无需定义可变参数模板来处理它(这是常见的实现对于 std 库函数)。

#include <functional>
#include <iostream>

double finder(double a, double b = 0) {
    return a + b;
};

template<typename Func, typename... Args>
double var_finder(double c, Func&& f, Args&&... args) {
    return c * std::forward<Func>(f)(std::forward<Args>(args)...);
};

template<typename Func, typename... Args>
double var_finder(double c, Func&& f) {
    return c * std::forward<Func>(f)();
};

class c_test
{
public:
    double finder(double a, double b = 0) {
        return a + b;
    };
};

int main () {
    double a = 2.0;
    double b = 0.0;

    // use as usual
    std::cout << var_finder(0.1, finder, a, b) << std::endl;

    // use with lambda
    std::cout << var_finder(0.1, [a,b](){ return a+b; }) << std::endl;

    // lambda with member argument, less gruesome than a variadic template calling a member function
    c_test c;
    std::cout << var_finder(0.1, [a,b, &c](){ return c.finder(a,b); }) << std::endl;

    return 0;
}

Bonus Demo