Search code examples
c++templatesstd-function

C++ template class error: function returning a function


I want to make a simple logger which automatically runs a function and returns its value.
The class is defined as:

template <typename R, typename... Args>
class Logger3
{

    Logger3(function<R(Args...)> func,
            const string& name):
        func{func},
        name{name}
    {}
    R operator() (Args ...args)
    {
        cout << "Entering " << name << endl;
        R result = func(args...);
        cout << "Exiting " << name << endl;
        return result;
    }
    function<R(Args...)> func;
    string name;
};

I want to pass the following simple add function to the logger:

int add(int a, int b)
{
    cout<<"Add two value"<<endl;

    return a+b;
}

By calling it this way:

auto caller = Logger3<int(int,int)>(add,"test");

However, it generates the following errors:

error: function returning a function
  133 |     Logger3(function<R(Args...)> func,
      |     ^~~~~~~
decorator.h:138:7: error: function returning a function
  138 |     R operator() (Args ...args)
      |       ^~~~~~~~
decorator.h:145:26: error: function returning a function
  145 |     function<R(Args...)> func;

Solution

  • There are 3 issues in your code:

    1. The Logger3 class template requires R to be the return value of the function (and Args it's arguments).
      (R is not a function type as implied by your attempt to instantiate Logger3).
      Therefore instantiating the Logger3 in your case of a function that gets 2 ints and returns an int should be:
    auto caller = Logger3<int, int, int>(add, "test");
    
    1. Your Logger3 constructor should be public in order to invoke it from outside the class.

    2. For efficiency reasons, you should use std::forward to forward the arguments from operator() to your function. This will avoid copy of the arguments (more significant in cases where their types are more complex than ints).
      Note that in order for std::forward to work as expected, operator() has to be itself a variadic template using forwarding references (see below).

    Complete fixed version:

    #include <string>     // std::string
    #include <functional> // std::function
    #include <utility>    // std::forward, std::declval
    #include <iostream>   // std::cout
    
    template <typename R, typename... Args>
    class Logger3
    {
    public:
        Logger3(std::function<R(Args...)> func,
            const std::string& name) :
            func{ func },
            name{ name }
        {}
    
        // Template with forwarding references to avoid copies
        // 'typename' arg is for SFINAE, and only enables if a 
        // function accepting 'Args...' can evaluate with 'UArgs...'
        template <typename...UArgs,
                  typename = decltype(std::declval<R(*)(Args...)>()(std::declval<UArgs>()...))>
        R operator() (UArgs&&...args)
        {
            std::cout << "Entering " << name << std::endl;
            R result = func(std::forward<UArgs>(args)...);
            std::cout << "Exiting " << name << std::endl;
            return result;
        }
    private:
        std::function<R(Args...)> func;
        std::string name;
    };
    
    int add(int a, int b)
    {
        std::cout << "Add two value" << std::endl;
        return a + b;
    }
    
    int main()
    {
        auto caller = Logger3<int, int, int>(add, "test");
        auto res = caller(3, 4);
        std::cout << "result: " << res << std::endl;
        return 0;
    }
    

    Output:

    Entering test
    Add two value
    Exiting test
    result: 7
    

    Demo: Godbolt.

    A side note: better to avoid using namespace std - see here: Why is "using namespace std;" considered bad practice?.