Search code examples
c++c++11variadic-templatesstd-functioncallable

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.


Solution

  • 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