C++ template class error: function returning a function
Asked Answered
S

2

5

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;
Scourge answered 26/7, 2022 at 5:31 Comment(0)
M
8

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?.

Maybellemayberry answered 26/7, 2022 at 5:44 Comment(2)
Args in operator() is from the primary class template and not from a function template. This means that Args... is not a forwarding reference. If the function is defined to have arguments taken by-value, then there will always be a copy of the argument when calling operator(). You can avoid copies even if Args... are by value if operator() was itself a variadic template using forwarding references: compiler-explorer.com/z/6GfGjbnYP.Zach
@Human-Compiler thanks. I fixed my answer regarding the forwarding reference. I kept the way the main template args for Logger3 were defined in my answer so I did not need the partial specialization. The other answer in this post offers the solution with the partial specialization.Maybellemayberry
C
7

You need to use template class partial specialization to get the type of R.

template <typename F>
class Logger3;

template <typename R, typename... Args>
class Logger3<R(Args...)>
{
  // implementation details
};

which makes int match the template parameter R of the partial specialization when you explicitly specify Logger3<int(int,int)>.

Callisthenics answered 26/7, 2022 at 5:38 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.