模板的好友运算符 class

Friend operators for template class

我有一个模板 class,我向其声明了 2 个 operator+ 方法。他们的声明是:

1) const MyClass<T> operator+ (int num) const;

2) friend const MyClass<T> operator+ <>(int num, const MyClass<T>& other);

this FAQ 之后,.hpp 文件看起来像这样:

//forward declaration of the class.
template<class T>
class MyClass;

//forward declaration of the operator+
template<class T>
const MyClass<T> operator+ (int num, const MyClass<T>& other);

template<class T>
class MyClass {
public:
    ...
    const MyClass<T> operator+ (int num) const;
    friend const MyClass<T> operator+ <>(int num, const MyClass<T>& other);
    ...
};

(后来我有了这些方法的定义)。

请注意,2 operator+ 方法用于不同的情况:

第一个

MyClass mc;
mc+5;

第二个在

MyClass mc;
5+mc;

但出于某种原因,当我用 g++(版本 4.8.2,如果重要的话)编译它时,出现错误:

declaration of ‘operator+’ as non-function
   friend const MyClass<T> operator+ <>(int num, const MyClass<T>& other);

注意错误指的是friendoperator+方法

但是,如果我删除第一个 operator+ 方法的声明(即只留下 friend 方法),那么一切都可以正常编译!

这是怎么回事?

我认为这应该是这样的:

//forward declaration of the class.
template<class T>
class MyClass;

//forward declaration of the operator+
template<class T>
MyClass<T> operator+ (int num, const MyClass<T>& other);

template<class T>
class MyClass {
public:
    MyClass<T> operator+ (int num) const;
    template<typename P> friend  MyClass<P> operator+ (int num, const MyClass<P>& other);
};

返回 const MyClass<T> 没有意义,因为 const 无论如何都会被丢弃。

不知道为什么,但切换顺序使其编译(将尝试找到解释):

//forward declaration of the class.
template<class T>
class MyClass;

//forward declaration of the operator+
template<class T>
const MyClass<T> operator+(int num, const MyClass<T>& other);

template<class T>
class MyClass {
public:
  friend const MyClass<T> operator+<>(int num, const MyClass<T>& other);
  const MyClass<T> operator+ (int num) const;

};

编辑

感谢 Maksim 指出我的怀疑是正确的。发生的情况是两个运算符的名称发生冲突,从而导致错误。通常第二个运算符的名称中会有 class 名称,但是由于在 class 声明中您在 class 范围内,因此名称冲突。如果非 class 成员运算符位于不同的命名空间中,例如:

//forward declaration of the class.
template<class T>
class MyClass;

//forward declaration of the operator+
namespace Foo
{
template<class T>
const MyClass<T> operator+(int num, const MyClass<T>& other);
}
template<class T>
class MyClass {
public:
  const MyClass<T> operator+ (int num) const;
  friend const MyClass<T> Foo::operator+<>(int num, const MyClass<T>& other);

};

using namespace Foo;

int main()
{
  MyClass<int> a;
  5 + a;
}

这很好,因为名称会不同。另外,如果您将它们用作另一个对象的朋友,那也很好:

//forward declaration of the class.
template<class T>
class MyClass;

//forward declaration of the operator+
template<class T>
const MyClass<T> operator+(int num, const MyClass<T>& other);

template<class T>
class MyClass {
public:
  const MyClass<T> operator+ (int num) const;
};

template <typename T>
class Foo
{
friend const MyClass<T> operator+<>(int num, const MyClass<T>& other);
friend const MyClass<T> MyClass<T>::operator+(int num);
};

int main()
{
  MyClass<int> a;
  5 + a;
}

我希望这能解释这些例子说明原因。

在大多数情况下,这就是您真正想要的:

template<class T>
class MyClass {
public:
  MyClass operator+ (int num) const;
  friend MyClass operator+(int num, const MyClass& other) {
    return other+num;
  }
};

我做了一些更改,都是有意的。

我删除了 MyClass<T>operator+ 的前向声明,因为它们都不需要。

我将非会员 operator+ 设为内联好友,不再是模板。这就是我所说的 Koenig 运算符:它只能通过 MyClass 上的 ADL(参数相关查找)访问。它不是一个模板:但是为每个模板创建了一个独立的非模板函数 class.

因为这个函数几乎肯定会无意识地将它的参数转发给成员operator+,所以我就在那里内联了它。

最后,我从 return 类型中删除了 constconst in return types 除了阻止一些移动优化外几乎没有什么作用。

最终结果是代码更短、更简单、更易于使用。

当我想要一个工业质量的解决方案时,我经常更进一步:

template<class T>
class MyClass {
public:
  MyClass& operator+=(int num); // do actual work here
  template<class Self>
  friend MyClass operator+(int num, Self&& self) {
    auto tmp = std::forward<Self>(self); // perfect forwarding
    tmp += num; // delegate to +=
    return tmp; // elide return value
  }
  template<class Self>
  friend MyClass operator+(Self&& self, int num) {
    return num + std::forward<Self>(self); // DRY principle
  }
};

我将所有内容转发给成员 +=。上面的版本还可以完美转发MyClass+。虽然运算符看起来过于贪婪(我的意思是,它似乎根本没有将 Self 限制为 MyClass 类型!),因为它只能通过 ADL 在 MyClass<T> 上找到, self 必须 是对 MyClass!

实例的引用

clang 有趣的是 operator+ 需要 template<class Self, std::enable_if_t<std::is_class<std::decay_t<Self>>{}>* = nullptr>,因为它将对 operator+ 的一些限制视为硬错误,而不是 SFINAE 错误。

此策略基于其他运算符 += 样式运算符,并将 Koenig 运算符用于仅转发到 +=+ 样式运算符,适用于各种情况.

您甚至可以使用继承来剥离样板文件,将 += 转换为 +。这甚至不需要 CRTP。

struct plus_impl {
  template<class Self,
    std::enable_if_t<std::is_class<std::decay_t<Self>>{}>* = nullptr
  >
  friend std::decay_t<Self> operator+(int num, Self&& self) {
    auto tmp = std::forward<Self>(self); // perfect forward a copy
    tmp += num; // delegate to +=
    return tmp; // elide return value
  }
  template<class Self,
    std::enable_if_t<std::is_class<std::decay_t<Self>>{}>* = nullptr
  >
  friend std::decay_t<Self> operator+(Self&& self, int num) {
    return num + std::forward<Self>(self); // DRY principle
  }
};

template<class T>
class MyClass : public plus_impl {
public:
  MyClass& operator+=(int num){
      std::cout << "+=" << num << "\n";
      return *this;
  }
};

live example.

我们在这里使用 ADL 的魔力。从 plus_impl 继承意味着 MyClass<T> + intplus_impl 中找到 operator+。由于 operator+ 是模板运算符,它实际上将 MyClass<T> 作为 MyClass<T> 而不是 plus_imploperator+ 的实现然后使用 MyClass<T>+= 来完成工作。

我相信这种技术类似于 boost::operators 的工作方式,除了它使用 CRTP(我记得一些关于他们使用它的讨论,因为一些旧的编译器没有它就不能做正确的事情?)

这项技术的另一个有趣之处在于 MyClass<T> magically get + support.

的子classes