2

我有一个带有重载朋友运算符的模板。它工作得很好,但如果在一个范围内有另一个不相关但相似的运算符,它不会编译: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;

icc 和 MSVC 会产生类似的错误(即尝试使用product<int>::typevia operator&for )。Tensor<int>

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

Tensor如果我用它完全删除类operator&,代码就会编译。

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


我看到许多消息来源建议写作,friend fvMatrix<Type> operator& <>即没有Type之间<>http://www.parashift.com/c++-faq-lite/template-friends.html,C++模板友元运算符重载),这确实解决了这个问题。

然而,即使是https://stackoverflow.com/a/4661372/3216312上的评论也使用friend std::ostream& operator<< <T>

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


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

4

2 回答 2

3

序言与一些标准

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

14.5.4 朋友 [temp.friend]

1 类或类模板的朋友可以是函数模板或类模板,函数模板或类模板的特化,或普通(非模板)函数或类。对于不是模板声明的友元函数声明:

— 如果朋友的名字是一个合格或不合格的模板ID,朋友声明是指一个函数模板的特化,否则

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

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

3 [...]如果朋友声明中的名称既不是限定的也不是模板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 在其 declarator-id 指代函数模板的特化的声明中,执行模板参数推导以识别该声明所指的特化。具体来说,这是针对显式实例化 (14.7.2)、显式特化 (14.7.3)和某些友元声明 (14.5.4) 完成的。

其中幸存的参数推导由臭名昭著的 SFINAE(替换失败不是错误)子句管理,该子句仅适用于直接上下文:

14.8.2 模板参数推导[temp.deduct]

8 [...] 如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是一种格式错误的类型或表达式,如果使用替换参数编写,则需要进行诊断。[ 注意:如果不需要诊断,程序仍然是格式错误的。访问检查是替换过程的一部分。— end note ] 只有在函数类型及其模板参数类型的直接上下文中的无效类型和表达式会导致推导失败。

将标准应用于您的示例

在您帖子的所有变体中,argument-dependent-lookup 将在类模板operator&的关联全局命名空间中找到两个重载。fvMatrix然后这些重载必须进行参数推导和偏序:

  1. 第一个示例(代码片段):因为您有friend ... operator& <Type> (...),所以没有参数推导,而是简单地替换Cmpt=intand Type=int,这会产生product<int>::type inside product2的无效类型。这不是直接的上下文,因此是一个硬错误。删除Tensor类模板当然也会删除错误。
  2. 第二个示例:如上,但使用typename product<Cmpt>::type而不是作为onproduct2<Cmpt::type的返回类型。在这里,无效类型在直接上下文中,您会收到 SFINAE 软错误,并且选择了有效类型。operator&Tensor<Cmpt>operator&fvMatrix<Type>
  3. 第三个例子:作为第一个但与friend ... operator& <> (...)。这需要参数推导,现在返回类型的原始operator&on实际上是无害的,因为参数推导本身失败(没有可以使等于 的模板)并且没有可以产生硬错误的替代。Tensorproduct2::typeCmptTensor<Cmpt>fvMatrix<int>

避免这些微妙之处的最佳方法

因为根本原因是不相关的运算符重载污染了全局命名空间,所以解决方法很简单:将每个类模板包装在自己的命名空间中!例如Tensor<Cmpt>进入namespace N1fvMatrix<Type>进入namespace N2。然后里面的朋友声明fvMatrix将找不到operator&forTensor<Cmpt>并且一切正常。

于 2014-02-06T14:34:40.003 回答
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>'

是因为 for 的定义为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;.

于 2014-02-06T15:48:29.003 回答