Issue
Let's say I have a variadic function template taking a function pointer to a function with said variadic arguments. The following code does not compile under gcc (11.2), but compiles under clang and msvc (https://godbolt.org/z/TWbEKWb9f).
#include <type_traits>
void dummyFunc(const int);
template<typename... Args>
void callFunc(void(*)(Args...), Args&&...); // <- this one is problematic
// see https://stackoverflow.com/questions/67081700/variadic-template-qualifiers
template<typename... Args>
void callFunc2(void(*)(std::conditional_t<std::is_const_v<Args>, const Args, Args>...), Args&&...); // <- this one works
int main()
{
// fails on gcc, works on clang and msvc
callFunc<const int>(&dummyFunc, 2);
// this works
//callFunc(&dummyFunc, 2);
// this works as well
//callFunc2<const int>(&dummyFunc, 2);
}
Specifying the function arguments 'Args...' explicitly as 'const int' prevents gcc from compiling the code. Apparently the template parameter expansion looses the cv-qualifier under gcc, while it is kept under clang and msvc. The error message is:
<source>: In function 'int main()':
<source>:15:24: error: no matching function for call to 'callFunc<const int>(void (*)(int), int)'
15 | callFunc<const int>(&dummyFunc, 2);
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
<source>:6:6: note: candidate: 'template<class ... Args> void callFunc(void (*)(Args ...), Args&& ...)'
6 | void callFunc(void(*)(Args...), Args&&...);
| ^~~~~~~~
<source>:6:6: note: template argument deduction/substitution failed:
<source>:15:24: note: types 'const int' and 'int' have incompatible cv-qualifiers
15 | callFunc<const int>(&dummyFunc, 2);
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
I found the 'solution' by utilizing type traits to make it compile here (Variadic template qualifiers), but I either should have to use this method or not, independently of the compiler. I know I don't have to specify the template arguments explicitly, but I want to understand why gcc fails to compile the code. Which compiler is "right", i.e. should this code compile or not (that is, should the cv-qualifier be dropped)?
As a side note, if I use a const reference instead, it compiles also under gcc, and the template argument is correctly recognized as 'const int&' (because the compiler absolutely has to do so). I am aware of the fact that, if the template argument is deduced by the compiler and not explicitly specified, that the cv-qualifier is dropped. But in my opinion gcc's behavior here is wrong because I explicitly stated the type to use. However, I don't know the standard well enough to tell whether gcc or the other two compilers are not following the standard. It seems to be related to variadic templates and not the template deduction itself, because the version without variadic templates works under gcc (https://godbolt.org/z/qn8a5bh5E):
void dummyFunc(const int);
template<typename Arg>
void callFunc(void(*)(Arg), Arg&&);
int main()
{
callFunc<const int>(dummyFunc, 2);
}
Also, why is automatic template type deduction even working in the first (problematic) case? 'Args...' should be deduced as both 'const int', because of the function pointer, as well as 'int' because of the second argument. My guess is that in this case, 'Args...' is deduced as 'const int' (otherwise it would not compile). Is my guess correct? It would be great if somebody could hint me to the relevant section in the standard.
Solution
The cv-qualifier should always be dropped (both in determining the type of dummyFunc
and when substituting the deduced argument into the callFunc
signature), and I'm pretty sure all compilers agree on this. It's not really what the question is about. Let's change the example a bit:
template <class... Args> struct S {
template <class... T> S(T...) {}
};
template<typename... Args>
void callFunc2(S<Args...>);
int main()
{
callFunc2<const int>(S<int>{});
}
Now GCC and Clang reject the code while MSVC accepts it.
GCC and Clang both have issues with the mismatch between const int
(explicitly specified) and int
(deduced) whereas MSVC is evidently happy just to let Args
= [const int
] as specified. Who is right?
As I see it the problem here is [temp.arg.explicit]/9, which states:
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.
Thus, specifying an explicit template argument for the pack Args
does not prevent deduction. The compiler still has to try to deduce Args
in case it needs to be extended in order to match the function parameter types against the argument types.
It has never been clearly explained what this paragraph of the standard really means. I guess MSVC's approach could possibly be something like "deduce the pack as if there were no explicitly specified template arguments, then throw out the result if the explicitly specified template arguments are not a prefix of the deduced template arguments" which seems like a sensible way to handle this code. GCC and Clang might be something like "deduce the pack as if there were no explicitly specified template arguments, and then fail if the explicitly specified template arguments are not a prefix of the deduced template arguments" which would lead to the code being ill-formed, but this seems like an unfortunate interpretation because it's inconsistent with how explicitly specified template arguments are treated in non-variadic cases.
The example above is similar to a simplified version of the OP's example:
void dummyFunc(int);
template<typename... Args>
void callFunc(void(*)(Args...));
int main()
{
callFunc<const int>(&dummyFunc);
}
Here, the trailing Args&&...
has been removed, which doesn't change the result: as with OP's code, Clang and MSVC accept it while GCC doesn't.. Only Clang has changed its opinion: it accepts this one while rejecting the one with S
. To be fair, these two snippets are not really analogous: the one with S
involves an implicit conversion. But it's not clear why Clang treats them differently.
From my point of view, GCC and Clang both have different bugs with variadic template deduction, while MSVC does the right thing in both cases. But it's hard to make an argument based on the standard text that this is unambiguously the case.
Answered By - Brian Bi Answer Checked By - Mary Flores (WPSolving Volunteer)