c++clangvariadic-templatesoverload-resolutioncompiler-bug

Do template parameter packs affect overload resolution?


Look at this code (godbolt):

template <typename ...T>
void func(const char *it, T &&...t) {}

template <typename A, typename B>
void func(A &&a, B &&b) {}

int main() {
    func("a", 0);
}

Clang doesn't compile this code (because the func call is ambiguous), while gcc compiles it. If I remove the variadic parameter, and use a simple typename T, then clang compiles the code.

Which compiler is correct? If clang is correct then please explain why there is a difference between the variadic and non-variadic case regarding overload resolution.


Solution

  • Do template parameter packs affect overload resolution?

    Generally yes, but it wouldn't matter in your example. [temp.deduct.partial] describes the specifics. For example, (T) would be more specialized than (T...).

    In your example

    template <typename ...T>
    void func(const char *it, T &&...t) {}
    
    template <typename A, typename B>
    void func(A &&a, B &&b) {}
    

    ... with two call arguments "a", 0, the compiler would attempt to synthesize types for T..., A, and B, and see if deduction would succeed if the other function was called with arguments of that type. const char* can't generally accept any hypothetical type A, and a single B can't generally accept a pack of hypothetical Ts, so deduction fails both ways. Therefore, neither template is more specialized and the call is ambiguous.

    GCC is wrong to prefer the second overload, where A = const char(&)[2], as can be seen in the assembly output:

    call void func<char const (&) [2], int>(char const (&) [2], int&&)
    

    This appears to be GCC bug 41958

    In other words, Clang is correct, and GCC has a bug.


    Note 1: There is a related Clang issue 47604, as @cigien has pointed out, but this doesn't seem to be relevant here and only manifests itself when producing a function pointer from an overloaded function, and the rules there are slightly different because there are no call arguments.

    Note 2: Also, [over.ics.scs] states that array-to-pointer conversions (such as from "" to const char*) is an Exact Match conversion, so it's not any worse than A&& binding to const char(&)[1].