Preamble with some Standardese
Your friend
declaration matches the first of the four clauses from [temp.friend]/1
(other 3 clauses omitted):
14.5.4 Friends [temp.friend]
1 A friend of a class or class template can be a function template or class template, a specialization of a function template or class template, or an ordinary (non-template) function or class. For a friend function declaration that is not a template declaration:
— if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise
Which names will be found by your friend declaration?
7.3.1.2 Namespace member definitions [namespace.memdef]
3 [...] If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace. [ Note: The other forms of friend declarations cannot declare a new member of the innermost enclosing namespace and thus follow the usual lookup rules. — end note ]
Because you have several overloads of operator&
, partial ordering is required:
14.5.6.2 Partial ordering of function templates [temp.func.order]
1 If a function template is overloaded, the use of a function template specialization might be ambiguous because template argument deduction (14.8.2) may associate the function template specialization with more than one function template declaration. Partial ordering of overloaded function template declarations is used in the following contexts to select the function template to which a function template specialization refers:
— when a friend function declaration (14.5.4), an explicit instantiation (14.7.2) or an explicit specialization (14.7.3) refers to a function template specialization.
and the set of candidates is as usual determined by a set of functions that survive argument template deduction:
14.8.2.6 Deducing template arguments from a function declaration [temp.deduct.decl]
1 In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations (14.7.2), explicit specializations (14.7.3), and certain friend declarations (14.5.4).
where surviving argument deduction is governed by the infamous SFINAE (Substition failure is not an error) clause that applies only to the immediate context:
14.8.2 Template argument deduction [temp.deduct]
8 [...] If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. — end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.
Applying the Standard to your example
In all variations on your post, argument-dependent-lookup will find two overloads of operator&
in the associated globabl namespace of the fvMatrix
class template. These overloads then have to play argument-deduction and partial ordering:
- first example (code fragment): because you have
friend ... operator& <Type> (...)
, there is no argument deduction but simple substitution ofCmpt=int
andType=int
, which yields an invalid type forproduct<int>::type
insideproduct2
. This is not in the immediate context and therefore a hard error. Removing theTensor
class template of course also removes the error. - second example: as above but with
typename product<Cmpt>::type
instead ofproduct2<Cmpt::type
as the return type of theoperator&
onTensor<Cmpt>
. Here, the invalid type is in the immediate context, and you get a SFINAE soft error and the validoperator&
forfvMatrix<Type>
is selected. - third example: as the first but with
friend ... operator& <> (...)
. This requires argument deduction and now the originaloperator&
onTensor
with theproduct2::type
return type is actually harmless because argument-deduction itself fails (there is no templateCmpt
that can makeTensor<Cmpt>
equal tofvMatrix<int>
) and there is no substition that can yield the hard error.
The best way to avoid these subtleties
Because the root cause is polluttion of the global namespace by unrelated operator overloads, the cure is simple: wrap each class template inside its own namespace! E.g. Tensor<Cmpt>
into namespace N1
and fvMatrix<Type>
into namespace N2
. Then the friend declaration inside fvMatrix
will not find the operator&
for Tensor<Cmpt>
and all works fine.