Search code examples
c++templatesc++17variadic-templatesstd-function

Why does template parameter unpacking sometimes not work for std::function?


I encountered a problem. When I use something like std::function<A(Fs...)> it doesn't work, but std::function<A(Fs..., B)> does work. This is under Clang 8.0; none of it works under GCC. Here is the example:

#include <functional>
template<typename A, typename B, typename ...Fs>
void func_tmpl1(std::function<A(Fs..., B)> callable)
{
}
template<typename A, typename ...Fs>
void func_tmpl2(std::function<A(Fs...)> callable)
{
}
class Cls1{};
void func0(std::function<void(float, Cls1)> callable)
{

}

int main()
{
    std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
    func0(f1);
    func0([](float a, Cls1 b){});
    func_tmpl1<void, Cls1, float>(f1); // fails in GCC
    func_tmpl2<void, float, Cls1>(f1);

    func_tmpl1<void, Cls1, float>( // fails in GCC
        [](float a, Cls1 b)
        {

        }
    );
    func_tmpl2<void, float, Cls1>( // fails in both
        [](float a, Cls1 b)
        {}
    );

    return 0;
}

On Godbolt, we can see GCC always fails, but Clang only fails at the last function call. Can anyone explain what's happening here?


Solution

  • For convenience, let's call the three failed calls in your code #1, #2 and #3.

    The problem is, when template arguments corresponding to a template parameter pack are explicitly specified, does the template parameter pack still participates in template argument deduction, and if it does, does deduction fail makes the whole call ill-formed?

    From [temp.arg.explicit]/9:

    Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments.

    We can infer that the template argument deduction should still be performed.

    In the declaration of func_tmpl1, std::function<A(Fs..., B)> is a non-deduced context ([temp.deduct.type]/9: "If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context."), so template argument deduction for Fs should be ignored and #1 and #2 are both well-formed. There is a GCC bug report.

    For #3, template argument deduction obviously fails (std::function<A(Fs...)> vs a lambda type), but does deduction fail really make the code ill-formed? In my opinion, the standard is unclear about this, and there is a related issue. From the response of CWG, #3 is indeed ill-formed.