Search code examples
c++c++11variadic-templates

Call function with variadic arguments where variadic arugments are references


I try to implement a class which accepts a function and a variadic argument list as input to execute these functions later on a worker thread. My current implementation has a problem, if one of the arguments is a reference.

Have a look at the following smaller code example:

#include <functional>
#include <iostream>

template<typename Result, typename ...Args>
Result Foo(Result f(Args...), Args... args)
{
    return f(args...);
}

int BarValue(int x){return x;}
int BarPointer(int* x){ *x++;return *x; }
int BarRef(int& x){ x++; return x; }

int main()
{
    int x{0};
    std::cout << Foo(BarValue, x)           << std::endl;
    std::cout << Foo(BarPointer, &x)        << std::endl;
    std::cout << Foo(BarRef, x)             << std::endl; // does not compile: Error 1
    std::cout << Foo(BarRef, std::ref(x))   << std::endl; // does also not compile: Error 2
    return 0;
}

Error 1:

<source>: In function 'int main()':
<source>:31:31: error: no matching function for call to 'Foo(int (&)(int&), int&)'
   31 |     std::cout << Foo(BarRef, x)             << std::endl;
      |                               ^
<source>:4:8: note: candidate: 'template<class Result, class ... Args> Result Foo(Result (*)(Args ...), Args ...)'
    4 | Result Foo(Result f(Args...), Args... args)
      |        ^~~
<source>:4:8: note:   template argument deduction/substitution failed:
<source>:31:31: note:   inconsistent parameter pack deduction with 'int&' and 'int'
   31 |     std::cout << Foo(BarRef, x)             << std::endl;
      |                               ^
ASM generation compiler returned: 1
<source>: In function 'int main()':
<source>:31:31: error: no matching function for call to 'Foo(int (&)(int&), int&)'
   31 |     std::cout << Foo(BarRef, x)             << std::endl;
      |                               ^
<source>:4:8: note: candidate: 'template<class Result, class ... Args> Result Foo(Result (*)(Args ...), Args ...)'
    4 | Result Foo(Result f(Args...), Args... args)
      |        ^~~
<source>:4:8: note:   template argument deduction/substitution failed:
<source>:31:31: note:   inconsistent parameter pack deduction with 'int&' and 'int'
   31 |     std::cout << Foo(BarRef, x)             << std::endl;
      |  

                         ^

Error 2:

<source>: In function 'int main()':
<source>:33:41: error: no matching function for call to 'Foo(int (&)(int&), std::reference_wrapper<int>)'
   33 |     std::cout << Foo(BarRef, std::ref(x))   << std::endl;
      |                                         ^
<source>:5:8: note: candidate: 'template<class Result, class ... Args> Result Foo(Result (*)(Args ...), Args ...)'
    5 | Result Foo(Result f(Args...), Args... args)
      |        ^~~
<source>:5:8: note:   template argument deduction/substitution failed:
<source>:33:41: note:   inconsistent parameter pack deduction with 'int&' and 'std::reference_wrapper<int>'
   33 |     std::cout << Foo(BarRef, std::ref(x))   << std::endl;
      |                                         ^
ASM generation compiler returned: 1
<source>: In function 'int main()':
<source>:33:41: error: no matching function for call to 'Foo(int (&)(int&), std::reference_wrapper<int>)'
   33 |     std::cout << Foo(BarRef, std::ref(x))   << std::endl;
      |                                         ^
<source>:5:8: note: candidate: 'template<class Result, class ... Args> Result Foo(Result (*)(Args ...), Args ...)'
    5 | Result Foo(Result f(Args...), Args... args)
      |        ^~~
<source>:5:8: note:   template argument deduction/substitution failed:
<source>:33:41: note:   inconsistent parameter pack deduction with 'int&' and 'std::reference_wrapper<int>'
   33 |     std::cout << Foo(BarRef, std::ref(x))   << std::endl;
      |                                         ^
Execution build compiler returned: 1

Compiled with gcc 10.2 and -O3 -std=c++17 : GodBolt

How can I solve this reference problem?


Solution

  • My recommendation is that you take a look at how the standard library itself uses templates to pass callable objects (functions, lambdas, etc.): By using a single template type:

    template<typename Func, typename ...Args>
    auto Foo(Func f, Args&&... args)
    {
        return f(std::forward<Args>(args)...);
    }
    

    Note that I have added a call to std::forward to properly "forward" the arguments.

    Also note that I made the return-type auto to let the compiler deduce the return type.


    If I understand your comments correctly, you want to create a variable that holds the returned value of f, and then return that variable later? Then you could either do it using decltype as you do in your compiler-explorer link, or just use plain auto again when defining and initializing your variable:

    template<typename Func, typename ...Args>
    auto Foo(Func f, Args&&... args)
    {
        auto value = f(std::forward<Args>(args)...);
    
        // Do something with the variable value...
    
        return value;
    }
    

    This will of course not work if the function f have a void return value.