3

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?

tjalling
  • 453
  • 5
  • 16

1 Answers1

2

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
}
cigien
  • 57,834
  • 11
  • 73
  • 112
  • That did it, thanks! How did you come up with the identity template solution? Is that a known pattern? – tjalling Apr 01 '20 at 20:21
  • Absolutely, in fact it's [coming](https://en.cppreference.com/w/cpp/types/type_identity) in c++20 – cigien Apr 01 '20 at 20:22
  • Ah, `type_identity can be used to establish non-deduced contexts in template argument deduction`. Useful note :) – tjalling Apr 01 '20 at 20:26
  • As far as I can tell, that's its entire purpose :) – cigien Apr 01 '20 at 20:27
  • Further reading: section '_Non-deduced contexts_' of ['Template argument deduction' at cppreference.com](https://en.cppreference.com/w/cpp/language/template_argument_deduction) – tjalling Apr 01 '20 at 21:16