Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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? =)

like image 589
user3026691 Avatar asked Nov 24 '13 07:11

user3026691


2 Answers

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);
like image 112
Guilherme Bernal Avatar answered Sep 25 '22 20:09

Guilherme Bernal


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) {}
like image 26
Felix Glas Avatar answered Sep 25 '22 20:09

Felix Glas