【问题标题】:About the return type of an unbound template friend function of a template class关于模板类的未绑定模板友元函数的返回类型
【发布时间】:2015-07-13 13:47:14
【问题描述】:

假设我有一个模板类,它有一个模板友元函数希望实现一个值乘以一个数组(myArray)的函数:

template<typename T, int size>
class myArray{
  T *_array;
public:
  ...
  template<typename Val, typename Array> 
  friend myArray<T,size> operator*(const Val &lhs, const Array &rhs){
    myArray<T,size> mat_t;
    for(int i = 0;i < size; i++)
      mat_t._array[i] = lhs * rhs._array[i];
    return mat_t;
}
  ...
};

它适用于 VS2013。然后我把未绑定模板友元函数的定义移到外面:

template<typename Val, typename Array>
myArray<T,size> operator*(const Val &lhs, const Array &rhs){
  myArray<T,size> mat_t;
  for(int i = 0;i < size; i++)
    mat_t._array[i] = lhs * rhs._array[i];
  return mat_t;
}

这是不正确的!我想问题出在朋友函数的返回类型上。但我想不通。 那么如何在类声明之外定义这样的友元模板函数呢?

【问题讨论】:

  • 您有问题吗?
  • 模板的朋友可以notoriously difficult搞定。为第二种情况发布您的错误和朋友声明。
  • 您希望 T 和尺寸是多少?你想支持 Array 是什么?也许您只希望 Array 成为另一个 myArray 在这种情况下,您应该指定它。但是如果你期望函数的返回类型独立于输入类型而变化,那么你需要指定它(然后它不能被推断出来)。
  • @tobi303 我的问题是如何在类声明之外定义友元函数。
  • @TartanLlama 朋友声明在上面的类中,只是删除了定义。错误类似于“T 无效且大小未声明”。

标签: c++ class templates c++11 friend-function


【解决方案1】:

friend 像第一个例子一样声明的函数是奇怪的野兽。 myArray&lt;T,Size&gt; 的每个实例都会创建一个distinct friend 函数,该函数只能通过myArray&lt;T,Size&gt; 上的参数相关查找来找到。

免费的operator* 没有这样声明是行不通的。

你可以让你的代码像这样工作:

template<class Val, class T, int size>
myArray<T,size> operator*(const Val &lhs, const myArray<T,size> &rhs){
  myArray<T,size> mat_t;
  for(int i = 0;i < size; i++)
    mat_t._array[i] = lhs * rhs._array[i];
  return mat_t;
}

这里所有的模板参数都列在template&lt;&gt;列表中,它们都可以从*的参数中推导出来。

记得将operator*myArray 放在同一个命名空间中。

但是,就个人而言,我会选择:

friend myArray operator*(const T&lhs, const myArray &rhs){
  myArray mat_t;
  for(int i = 0;i < size; i++)
    mat_t._array[i] = lhs * rhs._array[i];
  return mat_t;
}

template 的朋友operator*。同样,其中一个是为每个myArray&lt;T,size&gt;“生成”的,但它本身不是template。这有一定的优势,比如它在转换构造函数中表现得更好。

更进一步,我们得到:

friend myArray& operator*=(myArray&mat, const T&scalar){
  for(int i = 0;i < size; i++)
    mat._array[i] *= scalar;
  return mat;
}
friend myArray operator*(const T&scalar, myArray mat){
  mat *= scalar;
  return mat;
}
friend myArray operator*(myArray mat, const T&scalar){
  mat *= scalar;
  return mat;
}

我们首先创建一个*=,然后根据它写*。请注意,* 采用 myArray 的值(因为无论如何我都需要返回一个副本,不妨让它发生在方法之外),并且我同时支持 mat*scalarscalar*matmat*=scalar

另请注意,矩阵的矩阵......只是工作。

friend operator 是一个好主意的原因在 here 中进行了说明。请注意,代码无法编译。现在#define ADL to move the operator* into the class as friends 编译。 Koenig 运算符让 operator* 可以在 template 类上运行而不是模板,并且模板的参数匹配规则很难在没有过窄、过宽或使用讨厌的 SFINAE 的情况下正确。

下一步,operator*= 可以通过以下方式改进:(为简洁起见,使用 C++14)

template<class Array
  class=std::enable_if_t<std::is_same<std::decay_t<Array>,myArray>>
>
friend Array operator*=(Array&&mat, const T&scalar){
  for(int i = 0;i < size; i++)
    mat._array[i] *= scalar;
  return std::forward<Array>(mat);
}

这更复杂,但通过完美的转发做有趣的事情。临时的myArraymoved 放入返回值(允许引用生命周期延长工作),而非临时的myArray 返回一个引用。

这提供了为什么即使*= 也应该是friend 的原因。它允许在同一个方法中实现 r 值和左值。

另一方面,您可能不希望*= 使用右值。在这种情况下,请使用带有 &amp; 参考类别限定符的常规方法来消除该选择。

【讨论】:

  • operator*= 没有必要成为一个免费的朋友函数,它可以而且应该只是一个普通的成员函数。 operator* 也没有必要成为朋友,除非您决定不公开 operator*=
  • @ArneMertz 我将这种技术称为 Koenig 运算符。 this 编译,而 this 不编译,唯一的区别是我们有一个 Koenig friend operator*,而另一种情况我们有一个免费的 template operator*。将friend operators 与template 类型一起使用是有充分理由的:template 函数往往未指定或过度指定,或者具有讨厌的 SFINAE 要求。 operator*= 可能是一个普通的老方法,但完美的转发自我也很摇滚。
  • @Yakk 我只是无法让您的第一个示例正常工作。在以下示例中,您确实使用非模板友元函数提供了一个很好的解决方案,但是在外部定义时也存在问题。使用模板友元函数,我刚刚通过将声明更改为template&lt;typename Val, typename Array&gt; friend Array operator*(const Val &amp;lhs, const Array &amp;rhs) 并在正文中定义mat_t 类型为Array 来解决这个问题。但是如何在外面定义你的非模板友元函数呢?
  • @Shindou 不能在外部定义非模板友元函数:你能做的最好的就是调度(调用)一些其他的外部函数(或方法)。通常我将 += 称为“increment_by”,然后将 operator*=operator* 实现为 1-3 行函数。
【解决方案2】:

TsizemyArray的模板参数。如果您将包含这些模板参数的函数定义放在myArray 的定义之外,则必须再次声明它们是模板参数,例如

template <typename T, int size, typename Val, typename Array>

但是,即使您编译它也对您没有多大帮助,因为您将无法实际正确调用运算符,因为编译器无法推断出模板参数 T 和 @987654330 @。这个电话必须是像

这样尴尬的东西
auto a = operator*<double, 4>(22, someArray);

您可能希望创建一个只返回与其第二个参数相同类型的函数 - 无论如何其他任何事情都没有多大意义。为了避免模板友元声明的困难(很难做到正确)以及与友元的紧密耦合,您可以将运算符中继到公共乘法函数,或者更好的是 operator*=

template<typename T, int size>
class myArray {
  T *_array;
public:
  myArray() : _array(new T[size]{22}) {}
  //...
  template<typename Val> 
  myArray& operator*=(const Val &val) {
    for (int i = 0; i < size; ++i) 
      _array[i] *= val  ;
    return *this;
  }
};

template <typename Val, typename T, int size>
myArray<T,size> operator*(const Val &lhs, myArray<T, size> tmp) {
  tmp *= lhs; 
  return tmp;
}      

关于为什么operator*= 应该是一个成员函数而不是一个免费的朋友函数,请参阅this link

【讨论】:

  • 您能否演示一个隐式类型转换为myArray&amp; 的示例,该示例将其绑定到一个临时对象并导致friend operator*= 出现问题?在要求它是左值和只能通过 Koenig 查找访问之间,这似乎是不可能的。
  • @Yakk 请参阅here 示例,了解转化如何导致意外调用operator*=
  • Y case 成员与否无关。 And the Z case doesn't work with a friend operator*= (Koenig style).。那么为什么更喜欢成员 *= 而不是 Koenig 风格的 friend *=
猜你喜欢
  • 2016-10-19
  • 2010-12-19
  • 2015-03-12
  • 2013-08-19
  • 1970-01-01
  • 2018-07-12
  • 2011-07-15
  • 1970-01-01
相关资源
最近更新 更多