我有一个带有重载的朋友操作员的模板。它运行良好,但是如果范围内有另一个不相关但相似的运算符,它就不会编译: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.html,C++ 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代码中遇到了这样的问题。
答案 0 :(得分:3)
您的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
重载。然后,这些重载必须发挥参数推导和部分排序:
friend ... operator& <Type> (...)
,所以没有参数推断,而是Cmpt=int
和Type=int
的简单替换, <{1}} product<int>::type
内的无效类型。这不是直接的背景,因此是一个难以理解的错误。删除product2
类模板当然也会删除错误。Tensor
代替typename product<Cmpt>::type
作为product2<Cmpt::type
operator&
的返回类型。此处,无效类型位于直接上下文中,并且您收到SFINAE软错误,并且选择了Tensor<Cmpt>
的有效operator&
。fvMatrix<Type>
。这需要参数推断,现在friend ... operator& <> (...)
上带有operator&
返回类型的原始Tensor
实际上是无害的,因为参数推导本身失败(没有可以制作的模板product2::type
Cmpt
等于Tensor<Cmpt>
)并且没有可以产生硬错误的子站。 因为根本原因是无关运算符重载对全局命名空间的污染,所以解决方法很简单:将每个类模板包装在自己的命名空间中!例如。 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;
。