c ++模板friend运算符与另一个类似的运算符

时间:2014-02-06 12:26:07

标签: c++ templates friend sfinae argument-deduction

我有一个带有重载的朋友操作员的模板。它运行良好,但是如果范围内有另一个不相关但相似的运算符,它就不会编译:g ++会产生奇怪的错误,icc和MSVC会产生类似的错误。

代码是:

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;
}

gcc 4.8.1的错误(类似于4.8.0和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;

类似的错误(即尝试通过product<int>::type operator&使用Tensor<int>)由icc和MSVC生成。

如果我更改代码以便使用product代替product2 operator&代替Tensor(取消注释注释行和注释运算符[1]),代码编译。

如果我使用Tensor完全删除了课程operator&,则代码会进行编译。

更新:完全删除m&m;行仍会使代码无法编译。


我看到许多来源建议在friend fvMatrix<Type> operator& <>http://www.parashift.com/c++-faq-lite/template-friends.htmlC++ template friend operator overloading之间写Type,即<>,这确实解决了这个问题。

但是,即使https://stackoverflow.com/a/4661372/3216312的审核也使用friend std::ostream& operator<< <T>

所以,问题是:为什么上面的代码不能编译?写friend fvMatrix<Type> operator& <Type>错了,为什么?


背景:我们正在修改OpenFOAM框架,并在使用friend ... operator& <Type>http://foam.sourceforge.net/docs/cpp/a04795_source.html,第484行)的原始OpenFOAM代码中遇到了这样的问题。

2 个答案:

答案 0 :(得分:3)

带有一些Standardese的序言

您的friend声明与[temp.friend]/1的四个子句中的第一个匹配(省略了其他3个子句):

14.5.4朋友[temp.friend]

  

1类或类模板的朋友可以是函数模板或   类模板,功能模板的专业化或类   模板或普通(非模板)函数或类。为一个   不是模板声明的友元函数声明:

     

- 如果   朋友的名字是一个合格或不合格的模板ID,   朋友声明是指功能模板的专业化,   否则

您的朋友声明会找到哪些名字?

7.3.1.2命名空间成员定义[namespace.memdef]

  

3 [...]如果朋友声明中的姓名既不合格也不合格   template-id ,声明是一个函数或一个   详细说明类型指定,查找是否确定实体   先前已宣布不得考虑任何范围以外的   最里面的封闭命名空间。 [注意:其他形式的朋友   声明无法声明最内层封闭的新成员   命名空间,因此遵循通常的查找规则。 - 结束说明]

因为您有几个operator&的重载,所以需要部分排序:

14.5.6.2功能模板的部分排序 [temp.func.order]

  

1如果函数模板过载,则使用函数模板   专业化可能不明确,因为模板参数推断   (14.8.2)可以将功能模板专业化与更多相关联   比一个函数模板声明。重载的部分排序   函数模板声明用于以下上下文中   选择功能模板所在的功能模板   专业化指:

     

- 当朋友功能时   声明(14.5.4),显式实例化(14.7.2)或   显式特化(14.7.3)是指函数模板   专业化

并且候选集合通常由一组在参数模板推导中存活的函数确定:

14.8.2.6从函数声明中减去模板参数[temp.deduct.decl]

  

1在声明中,声明者id指的是的特化   功能模板,执行模板参数推导   确定声明所涉及的专业化。   具体来说,这是为了显式实例化(14.7.2),   显式专业化(14.7.3),和某些朋友声明   (14.5.4)。

幸存的论证演绎由臭名昭着的SFINAE(替代失败不是错误)条款管理,该条款仅适用于直接背景:

<强> 14.8.2 模板参数演绎 [temp.deduct]

  

8 [...]如果替换导致无效的类型或表达式,则类型扣除失败。无效的类型或表达式   如果使用替代参数编写,则需要诊断的格式错误。 [ 注意:   如果不需要诊断,程序仍然是不正确的。访问检查是替换的一部分   处理。 - 结尾注释]只有函数类型的直接上下文中的无效类型和表达式   其模板参数类型可能导致演绎失败。

将标准应用于您的示例

在帖子的所有变体中,参数依赖查找将在operator&类模板的关联globabl命名空间中找到两个fvMatrix重载。然后,这些重载必须发挥参数推导和部分排序:

  1. 第一个示例(代码片段):因为您有friend ... operator& <Type> (...),所以没有参数推断,而是Cmpt=intType=int的简单替换, <{1}} product<int>::type内的无效类型。这不是直接的背景,因此是一个难以理解的错误。删除product2类模板当然也会删除错误。
  2. 第二个示例:如上所述,但Tensor代替typename product<Cmpt>::type作为product2<Cmpt::type operator&的返回类型。此处,无效类型位于直接上下文中,并且您收到SFINAE软错误,并且选择了Tensor<Cmpt>的有效operator&
  3. 第三个例子:作为第一个例子,但是fvMatrix<Type>。这需要参数推断,现在friend ... operator& <> (...)上带有operator&返回类型的原始Tensor实际上是无害的,因为参数推导本身失败(没有可以制作的模板product2::type Cmpt等于Tensor<Cmpt>)并且没有可以产生硬错误的子站。
  4. 避免这些微妙之处的最佳方法

    因为根本原因是无关运算符重载对全局命名空间的污染,所以解决方法很简单:将每个类模板包装在自己的命名空间中!例如。 fvMatrix<int> Tensor<Cmpt>namespace N1 fvMatrix<Type> namespace N2。然后fvMatrix中的朋友声明找不到operator&的{​​{1}},一切正常。

答案 1 :(得分:0)

据我所知,前两行

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

应替换为

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

我不完全确定这是否是您想要的,但这确实消除了编译器错误。 从根本上说,错误

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

是由product<int>的定义为空的事实引起的。 请注意,您的第一行定义了一个空的常规product<Type>,第二行只定义了

public: typedef double type;

作为product<double>的正文,而非普通product<Type>

新版本允许为所有类型创建类似的typedef

作为旁注,如果你改变

fvMatrix<int> m;

fvMatrix<double> m;

代码也会编译,因为product<double>确实包含public: typedef double type;