Who is right?
GCC is correct to accept all the cases, whereas Clang and MSVC are wrong to reject them; out-of class-definitions of partial specializations shall be matched to their declarations by equivalent template-head:s, which includes constraints, if any.
The template-head of the primary template of Foo
is not equivalent to the template-head of the two constrained partial specializations of it, and the definition of the primary template shall therefor not conflict with out-of-class definitions to member functions of constrained specializations of it.
Do I have a fundamental misconception about partial template specialization?
No, not from the looks of this question.
How do I get this working on MSVC?
As MSVC, like Clang, seems to (currently) have problems differentiating template-head:s for out-of-class definitions (particularly shown when partially specializing a class template by means of constraints), you will need to avoid the latter whilst compiling with MSVC (for now). As there may not be an associated MSVC bug report, you may want to consider filing one.
Details
As per [temp.spec.partial.general]/4 a partial specialization may indeed be constrained:
A partial specialization may be constrained ([temp.constr]). [Example
2:
template<typename T> concept C = true;
template<typename T> struct X { };
template<typename T> struct X<T*> { }; // #1
template<C T> struct X<T> { }; // #2
[...] — end example]
as long as the declaration of the primary template precedes it.
We may minimize your example into the following:
template <typename>
concept C = true;
template <typename T>
struct S;
template <C T>
struct S<T> { void f(); };
template <C T>
void S<T>::f() {}
which GCC accepts but Clang rejects
prog.cc:12:12: error: out-of-line definition of f
from class S<T>
without definition
void S<T>::f() {}
~~~~~~^
meaning Clang interprets the out-of-class definition
template <C T>
void S<T>::f() {}
as having a template-head that is equivalent to that of the primary template, which is wrong, as the template-head includes template-parameter:s which in turn includes type-parameter:s which in turn includes type-constraints:s, if any.
The equivalence of template-head:s is governed by [temp.over.link]/6, which includes constraints [emphasis mine]:
Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent and are both declared with type-constraints that are equivalent if either template-parameter is declared with a type-constraint, and if either template-head has
a requires-clause, they both have requires-clauses and the
corresponding constraint-expressions are equivalent.
Finally, albeit non-normative, both Example 2 of [temp.mem] and
[...] A member template can be defined within or outside its class
definition or class template definition. A member template of a class
template that is defined outside of its class template definition
shall be specified with a template-head equivalent to that of the class template followed by a template-head equivalent to that of
the member template ([temp.over.link]). [...]
[ Example 2:
template<typename T> concept C1 = true;
template<typename T> concept C2 = sizeof(T) <= 4;
template<C1 T> struct S {
template<C2 U> void f(U);
template<C2 U> void g(U);
};
template<C1 T> template<C2 U>
void S<T>::f(U) { } // OK
template<C1 T> template<typename U>
void S<T>::g(U) { } // error: no matching function in S<T>
— end example]
and Example 2 of [temp.class.general]:
[ Example 2:
// ...
template<typename T> concept C = true;
template<typename T> concept D = true;
template<C T> struct S {
void f();
void g();
void h();
template<D U> struct Inner;
};
template<C A> void S<A>::f() { } // OK: template-heads match
template<typename T> void S<T>::g() { } // error: no matching declaration for S<T>
template<typename T> requires C<T> // ill-formed, no diagnostic required: template-heads are
void S<T>::h() { } // functionally equivalent but not equivalent
template<C X> template<D Y>
struct S<X>::Inner { }; // OK
— end example]
show example of out-of-class definitions of member functions of constrained class template specializations.
This is, particularly, Clang bug:
I'm not aware whether there is a similar bug report for MSVC or not.