Question

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).

Was it helpful?

Solution

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:

  1. first example (code fragment): because you have 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.
  2. second example: as above but with 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.
  3. third example: as the first but with 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.

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.

OTHER TIPS

From what I can tell, the first two lines

template <class Type> class product {};
template <> class product<double> { public: typedef double type; };

should be replaced with

template <class Type> class product { public: typedef Type type; };

I'm not entirely sure if this is what you want, but this does get rid of the compiler error. Fundamentally, the error

c.cpp:5:50: error: no type named 'type' in 'class product<int>'

was caused by the fact that the definition for product<int> is empty. Notice that your first line defines a general product<Type> that is empty, and the second line only defines

public: typedef double type;

as the body of product<double>, not for a general product<Type>.

The new version of this allows a similar typedef to be created for all types.

As a side note, if you change

fvMatrix<int> m;

into

fvMatrix<double> m;

the code also compiles because product<double> does contain public: typedef double type;.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top