简体   繁体   中英

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

I'm learning variadic templates in C++11. How can I call test.finder as a functional argument of 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;
}

My expected result is 0.1 * (2 + 0) = 0.2.

Your solution has a lot of problems:

1) You can not convert a member function to a std::function , because the member function can never be called without an object. You can solve this problem by making the member function static or use std::bind or use a lambda.

2) You can not have a variadic argument list in the mid of method parameters. Variadic arguments must go to the end of the parameter list.

3) You can not deduce template arguments "on the fly" inside the parameter list itself. Solution: Use a new template parameter with a default template argument.

4) I see no chance to have default arguments for a function which we want to use in a std::function with reulting different signature double(double) vs 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;
}

You can simply do this:

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;
}

and the result is:

0.2
0.5

I guess you're a bit mixing the variadic template part with a bit of design-flaw

Let's start.

Preamble : the correct way to deal with variadic templates is to use rvalue-reference and std::forward to achieve perfect forwarding .

1) The easy way : you don't need class at all

you're actually not referring to any member so a class bring only complexity. It's better to refer to a free function for these cases

#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

your function accept 2 parameters so you have to pass zero as second argument.

2) Using a class

The problem with your attempt is you want to call c_test.var_finder with a function of c_test itself. Now you have to figure what kind of design you want. You can make 2 assumption. First is "I want anyway a finder function inside my class", then you have to make it static because it really does not use class member so you don't need an instance of c_test , right? so using a free function or a static member function leaves the var_finder implementation and you just have to call it this way

#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

second assumption you can do is "nope, I want any function member to be called with var_finder regardless where it comes from". I strongly discourage this approach because is carving a solution from a bad design, so I suggest to rethink your design to fall to solution 1 or 2.

3) Bonus : a nice design

You can add a non-variadic function and delegate the usage to the use of a lambda, which allow you to use a member function inside it without defining a variadic template to deal with that (and it is the common implementation for the std library functions).

#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

There are multiple things to deal with here.

First, put the parameter pack args at the end, otherwise the compiler doesn't know where to stop matching arguments with it (unless you specify the template arguments).

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

Second, don't name the parameter in std::function ; also just don't use std::function , use a function pointer instead.

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

Third, make finder static, otherwise you run into member function pointers and the syntax is more difficult to grasp (more on that later).

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

Third, provide the optional argument for finder . Otherwise, the parameter pack can't be deduced because it doesn't match both func and args .

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

This gives you the following.

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

If you really need finder to not be static, then you have to deal with member function pointer syntax.

First, the parameter func must be a member function pointer.

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

Then, you need to pass a c_test variable to var_finder in order to call 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;

That gives you the following code.

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

If you want to be able to call finder without specifying the optional parameter, then you need some intermediary, like a lambda.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM