user3026691
user3026691

Reputation: 497

Weird substitution failure with C++11 variadic template

I'm having a hard time figuring out what causes the substitution failure in this example code:

bool f(int a, int b, float c)
{
    printf("%d %d %f", a, b, c);
    return true;
}

template <typename ...Params>
void call1(Params... params, std::function<bool(Params...)> func)
{
    func(params...);
}

template <typename ...Params>
void call2(std::function<bool(Params...)> func)
{
}

Somewhere in main:

call1<int, int, float>(3, 4, 5.5, f); // Ok.
call2<int, int, float>(f); // Substitution failure.

The compiler says:

template argument deduction/substitution failed: mismatched types 'std::function<bool(Params ...)>' and 'bool (*)(int, int, float)'
call2<int, int, float>(f);
                          ^

What baffles me is that call1 works while call2 doesn't. Any tips? =)

Upvotes: 8

Views: 403

Answers (3)

Guilherme Bernal
Guilherme Bernal

Reputation: 8293

First: You can specify less arguments than what you use and let the compiler deduce the rest:

template <typename ...Params>
void func1(Params... params);

func1<int, int>(1, 2, 3); // calls func1<int, int, int>

This means Params is still open for adding extra types when you call it. But if you take the function address, it becomes defined and closed:

auto x = func1<int, int>;
x(1, 2, 3); // not possible

When you call your call1 function directly:

template <typename... Params>
void call1(Params... params, std::function<bool(Params...)> func);

call1<int, int, int>(1, 2, 3, f);
call1<int>(1, 2, 3, f); // same as before
call1(1, 2, 3, f); // same as before

The compiler is able to deduce that you have exactly 3 ints because you just sent him 3 ints. This way the last parameter must be std::function<bool(int, int, int)> because we fully deduced what Params... means and there's no space for more types.

Now the problematic case:

template <typename... Params>
void call2(std::function<bool(Params...)> func);

call2<int, int, int>(f);

Here you informed the compiler that the first 3 elements of Params are all ints. Params = {int, int, int, ...}. Note that it is still open for adding something else if deduction tells so. Replacing we have: std::function<bool(int, int, int, ...)> func. The compiler can't possibly know what this incomplete type means unless you explicitly pass a std:function (exact match). It doesn't know yet it can have a constructor taking the function pointer you provided, so there's a mismatch. Now the compiler does not have enough data to decide if it need more types into Params or not. Failure.

But note this interesting case:

auto x = call2<int, int, int>;
x(f); // x is exactly void(*)(std::function<bool(int, int, int)>). No doubts.

Here the you force Params to be complete. There's no deduction to evaluate. Although ugly, this also works:

(&call2<int, int, int>)(f);

Upvotes: 3

Felix Glas
Felix Glas

Reputation: 15524

The compiler can't deduce the type with your current code (even though the function pointer is implicitly convertible to a std::function). You could create a traits class to help deduce the correct type.

template <typename... Params>
struct func_traits {
    using func_type = std::function<bool(Params...)>;
};

template <typename ...Params>
void call2(typename func_traits<Params...>::func_type func) {}

Upvotes: 1

chill
chill

Reputation: 16888

The type of the expression f is a pointer to function, i.e. bool (*)(int, int, float). Yet the explicit instantiation of call2 template has type void foo (std::function<bool (int, int, float)> ), i.e. the parameter type is different, hence the mismatch.

Workaround:

auto x = call2<int, int, float>;
x(f);

taking advantage of the possibility to construct a std::function from a function pointer.

Upvotes: 0

Related Questions