I have a template with an overloaded friend operator. It works well, but if there is another unrelated but similar operator within a scope, it does not compile: g++ produces strange error, and a similar errors are produced by icc and MSVC.
The code is:
template <class Type> class product {};
template <> class product<double> { public: typedef double type; };
template<class Type> class product2 {
public: typedef typename product<Type>::type type;
};
//------------
template <class Cmpt> class Tensor { };
template <class Cmpt>
typename product2<Cmpt>::type operator&
(const Tensor<Cmpt>& a, const Tensor<Cmpt>& b)
{ return 0; } // [1]
//template <class Cmpt>
//typename product<Cmpt>::type operator&
//(const Tensor<Cmpt>& a, const Tensor<Cmpt>& b)
//{ return 0; }
//-----
template<class Type> class fvMatrix;
template<class Type>
fvMatrix<Type> operator&
(const fvMatrix<Type>& a, const fvMatrix<Type>& b)
{ return a; }
template <class Type> class fvMatrix {
friend fvMatrix<Type> operator& <Type>
(const fvMatrix<Type>& a, const fvMatrix<Type>& b);
};
//----------
int main() {
fvMatrix<int> m;
m & m;
return 0;
}
The error by gcc 4.8.1 is (similar for 4.8.0 and 4.7.2):
c.cpp: In instantiation of 'class product2<int>':
c.cpp:13:31: required by substitution of 'template<class Cmpt> typename product2<Type>::type operator&(const Tensor<Cmpt>&, const Tensor<Cmpt>&) [with Cmpt = int]'
c.cpp:32:27: required from 'class fvMatrix<int>'
c.cpp:39:17: required from here
c.cpp:5:50: error: no type named 'type' in 'class product<int>'
public: typedef typename product<Type>::type type;
Similar errors (that is, attempting to use product<int>::type
via operator&
for Tensor<int>
) are produced by icc and MSVC.
If I change the code so that product
is used instead or product2
in operator&
for Tensor
(uncomment the commented lines and comment operator [1]), the code compiles.
If I completely remove class Tensor
with its operator&
, the code compiles.
UPDATE: completely removing m&m;
line still leaves code not compiling.
I see that many sources suggest writing friend fvMatrix<Type> operator& <>
, i.e. without Type
between <>
(http://www.parashift.com/c++-faq-lite/template-friends.html, C++ template friend operator overloading), and this indeed solves this problem.
However, even the review at https://stackoverflow.com/a/4661372/3216312 uses friend std::ostream& operator<< <T>
So, the question is: why does the above code not compile? Is writing friend fvMatrix<Type> operator& <Type>
wrong and why?
Background: we are modifying OpenFOAM framework and ran into such a problem inside the original OpenFOAM code which uses friend ... operator& <Type>
(http://foam.sourceforge.net/docs/cpp/a04795_source.html, line 484).
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.
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:
friend ... operator& <Type> (...)
, there is no argument deduction but simple substitution of Cmpt=int
and Type=int
, which yields an invalid type for product<int>::type
inside product2
. This is not in the immediate context and therefore a hard error. Removing the Tensor
class template of course also removes the error.typename product<Cmpt>::type
instead of product2<Cmpt::type
as the return type of the operator&
on Tensor<Cmpt>
. Here, the invalid type is in the immediate context, and you get a SFINAE soft error and the valid operator&
for fvMatrix<Type>
is selected.friend ... operator& <> (...)
. This requires argument deduction and now the original operator&
on Tensor
with the product2::type
return type is actually harmless because argument-deduction itself fails (there is no template Cmpt
that can make Tensor<Cmpt>
equal to fvMatrix<int>
) and there is no substition that can yield the hard error. 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.