【问题标题】:c++ template friend operator with another similar operatorc++ 模板友元运算符与另一个类似运算符
【发布时间】:2014-02-06 12:26:06
【问题描述】:

我有一个带有重载友元运算符的模板。它工作得很好,但是如果一个范围内有另一个不相关但相似的运算符,它不会编译: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 会产生类似的错误(即尝试通过 operator&amp; 使用 product&lt;int&gt;::type 来代替 Tensor&lt;int&gt;)。

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

如果我完全删除类 Tensor 及其 operator&amp;,则代码编译。

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


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

然而,即使https://stackoverflow.com/a/4661372/3216312 的评论使用friend std::ostream&amp; operator&lt;&lt; &lt;T&gt;

那么,问题是:为什么上面的代码不能编译?写friend fvMatrix&lt;Type&gt; operator&amp; &lt;Type&gt; 错了吗?为什么?


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

【问题讨论】:

  • clang 3.4 也出现错误

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


【解决方案1】:

用一些标准语作为序言

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

14.5.4 朋友 [temp.friend]

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

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

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

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

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

因为operator&amp; 有多个重载,所以需要部分排序:

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 [...] 如果 替换导致无效的类型或表达式,则类型推导失败。无效的类型或表达式是 如果使用替换参数编写,则格式错误,需要诊断。 [ 笔记: 如果不需要诊断,则程序仍然是格式错误的。访问检查是作为替换的一部分完成的 过程。 — 尾注] 只有在函数类型的直接上下文中无效的类型和表达式和 其模板参数类型可能导致推演失败。

将标准应用于您的示例

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

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

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

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

【讨论】:

  • 为什么解决方案 &lt;&gt; 没有明确的 Type 有效?
  • 我还是不明白。我了解 ADL 仅指对函数的调用;但问题在于朋友定义。我在原始问题中错过了这一点,但我可以完全省略 m&amp;m; 遇到同样问题的行。这也不能解释为什么在Tensoroperator&amp; 工作中将product2 替换为product
  • @pkalinin 第一次尝试是错误的,看看新的尝试,我认为现在是正确的。
  • @TemplateRex,一个简短的总结,以了解我正确地理解了它。如果我在friend 声明中有&lt;Type&gt;,编译器会看到operator&amp;s 都与模板参数列表匹配,因此会尝试在那里替换Type。 SFINAE 在这种情况下可以工作,但前提是错误在直接上下文中,因此将消除返回 product 而不是 product2 的运算符。如果我有&lt;&gt;,那么Tensoroperator&amp; 会在模板参数推导过程中提前消除。
  • @pkalinin 正确总结,并注意一切都是由 ADL 和命名空间污染造成的。
【解决方案2】:

据我所知,前两行

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&lt;int&gt; 的定义为空。 请注意,您的第一行定义了一个空的通用 product&lt;Type&gt;,而第二行只定义了

public: typedef double type;

作为product&lt;double&gt; 的主体,不适用于一般的product&lt;Type&gt;

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

附带说明,如果你改变了

fvMatrix<int> m;

进入

fvMatrix<double> m;

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

【讨论】:

  • 是的,我完全了解你所说的一切。一般product是故意空的;关键是编译器根本不应该使用它。问题不是如何解决这个问题,而是为什么编译器会选择Tensoroperator&amp;,而主代码与Tensor 完全无关?
  • g++ 似乎正在为Tensor 编译operator&amp;,因为需要尝试检查它是否可能匹配。
  • 但是 SFINAE 应该可以工作,因此它应该不是问题。这并不能解释为什么用product 代替product2 有帮助,以及为什么用friend ...operator&amp;&lt;&gt; 代替friend... operator&amp;&lt;Type&gt; 有帮助。
猜你喜欢
  • 2011-04-28
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-18
  • 2011-06-07
  • 2010-11-20
相关资源
最近更新 更多