While creating this answer for another question I came around the following issue. Consider this program (godbolt):
#include <variant>
#include <iostream>
template <typename T>
struct TypeChecker {
void operator()() {
std::cout << "I am other type\n";
}
};
template<typename... Ts, template<typename...> typename V>
requires std::same_as<V<Ts...>, std::variant<Ts...>>
struct TypeChecker<V<Ts...>>
{
void operator()()
{
std::cout << "I am std::variant\n";
}
};
int main()
{
TypeChecker<std::variant<int, float>>{}();
TypeChecker<int>{}();
}
The output (which is also expected) is the following (with clang 14.0.0 as well as with gcc 12.1):
I am std::variant
I am other type
If however the three dots in the parameter list of the template template are removed, like this (whole program live on godbolt):
template<typename... Ts, template<typename> typename V>
,then the output is different for clang and gcc. The clang 14.0.0 compiled program outputs
I am other type
I am other type
whereas the gcc 12.1 compiled program outputs
I am std::variant
I am other type
It seems that using the non-variadic template template exhibits different matching rules in clang and gcc. So my question is, which behavior is correct if it is even well defined, and why?
Since defect report resolution P0522R0 was adopted, exact matching of template parameter lists for template template parameter match is no longer needed, and correct output according to the standard is:
In the current draft (which also contains changes related to C++20 concepts) relevant standard excerpts are temp.arg.template#3-4 (bold emphasis mine):
So, as we see, exact (up to special rules for parameter pack) parameter matching is now considered only in the case parameter list of template template parameter contains a pack (like your original example), otherwise only the new at least as specialized as relation is used to test matching, which defines for both template template parameter and argument respective function templates and tests whether parameter-induced function is at least as specialized as argument-induced function according to partial ordering rules for template functions.
In particular, matching A=
std::variantto P=template<typename> typename Vwe get these corresponding function templates:So, to prove A matches P, we need to prove
f(X<T>)is at least as specialized asf(X<Ts...>). temp.func.order#2-4 says:Transformed template of
f(X<T>)isf(X<U1>)and off(X<Ts...>)isf(X<U2FromPack>), whereU1andU2FromPackare two synthesized unique types. Now, temp.deduct.partial#2-4,8,10 says:Now, in our case, per 3.3, the function type itself is the only one considered among types to determine ordering. So, per 2, 8 and 10, to know whether
f(X<T>)is at least as specialized asf(X<Ts...>)we need to see whethervoid(X<T>)is at least as specialized asvoid(X<Ts...>), or, equivalently, whether deduction from type for P=void(X<Ts...>)from A=void(X<U1>)succeeds. It does according to temp.deduct.type#9-10:Here, per 10, comparison of functions types results in a single comparison of
X<Ts...>andX<U1>, which, according to 9, succeeds.Thus, deduction is succesful, so
f(X<T>)is indeed at least as specialized asf(X<Ts...>), sostd::variantmatchestemplate<typename> typename V. Intuitively, we gave 'more general' template template argument which should work nicely for intended usage of a more specific template template parameter.In practice, different compilers enable P0522R0 changes under different circumstances, and cppreference template parameters page (section Template template arguments) contains links and information on GCC, Cland and MSVC. In particular, GCC in C++17+ mode enables it by default (and for previous standards with compiler flag
fnew-ttp-matching), but Clang doesn't in any mode unless-frelaxed-template-template-argsflag is provided, thus you got the difference in output for them. With the flag, Clang also produces correct behaviour (godbolt).