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

Compiler unable to deduce template arguments for variadic template


Suppose I want to do partial function application to make a wide range of functions conform to a single signature.

For example, I could want to go from a double-parameter function to a single-parameter function as follows:

std::function<int(int, int)> doubleFoo = [](int i, int j) { return i + j; };
// Turn the function into a single-parameter function, using j = 5
std::function<int(int)> singleFoo = toSingleFoo(doubleFoo, 5);

As I want toSingleFoo to handle any single- or multi-argument function of which the first argument is an int, I've defined it as a variadic template function:

template <typename... Args>
std::function<int(int i)> toSingleFoo(std::function<int(int, Args&&...)> multiFoo, Args&&... args)
{
    auto singleFoo = [args](int i) { multiFoo(i, std::forward<Args>(args)...) };
    return singleFoo;
}

However, that gives the following compiler errors (using Visual Studio 2017, version 15.7.6):

error C2672: 'toSingleFoo': no matching overloaded function found
error C2784: 'std::function<int (int)> toSingleFoo(std::function<int(int,Args &&...)>,Args &&...)':
              could not deduce template argument for 'std::function<int(int,Args &&...)>' 
              from 'std::function<int (int,int)>'

Why is the compiler unable to deduce template arguments, despite an int being passed as second argument in the example above?


Solution

  • To start off, you need to capture multiFoo, as well as capture the variadic args....

    The issue with the deduction seems to be in the std::function argument. If you only allow it to deduce Args... from the second argument, deduction proceeds as expected.

    To hide the deduction of the first argument, just put it in an identity template

    template<typename T>
    struct I { using type = T; };
    

    Then you can define the function as

    template <typename... Args>
    std::function<int(int)> toSingleFoo(
                              typename I<std::function<int(int, Args&&...)>>::type multiFoo, 
                              Args&&... args)
    {
        return [multiFoo, &args...] (int i) {
            return multiFoo(i, std::forward<Args>(args)...); 
        };
    }
    

    and then use it

    int main() {
        std::function<int(int, int)> doubleFoo = [](int i, int j) { return i + j; };
        // Turn the function in a single-parameter function, using j = 5
        std::function<int(int)> singleFoo1 = toSingleFoo(doubleFoo, 5);
    
        std::cout << singleFoo1(3); // prints 8
    
        std::function<int(int, int, int)> tripleFoo = [](int i, int j, int k) { return i * j * k; };
        // Turn the function in a single-parameter function, using j = 2, k = 3
        std::function<int(int)> singleFoo2 = toSingleFoo(tripleFoo, 2, 3);
    
        std::cout << singleFoo2(4); // prints 24
    }