模板类的朋友操作员

时间:2015-08-18 12:51:08

标签: c++ templates c++11 operator-overloading friend-function

我有一个模板类,我向其声明了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);

请注意,错误是指friend operator+方法

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

发生了什么事?

3 个答案:

答案 0 :(得分:4)

不确定原因,但是切换顺序会使其编译(将尝试找到解释):

//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;

};

修改

感谢马克西姆指出我的怀疑是真的。会发生什么是两个运算符的名称冲突,从而发生错误。通常,第二个运算符会在其名称中包含类名,但由于在类声明中您在类范围内,因此名称会发生​​冲突。如果非类成员运算符位于不同的名称空间中,例如:

//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;
}

我希望这可以解释这些例子解释原因。

答案 1 :(得分:2)

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

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(参数相关查找)来访问。它不是模板:但是为每个模板类创建了一个独立的非模板函数。

由于这个函数几乎可以肯定地只是将其论据转发给成员operator+,所以我在那里直接内联。

最后,我从返回类型中删除了const。除了阻止一些移动优化之外,返回类型中的const几乎没有。

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

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

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类型!),因为它只能通过MyClass<T>上的ADL找到,self 必须是对MyClass的实例的引用!

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

此策略以+=样式运算符为基础的其他运算符,以及+样式运算符使用Koenig运算符,只转发到+=,适用于各种情况。< / p>

您甚至可以使用继承删除样板,将+=转换为+。这甚至不需要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> + intoperator+中找到plus_impl。由于operator+是模板运算符,它实际上将MyClass<T>作为MyClass<T>,而不是plus_imploperator+的实施然后使用MyClass<T>的{​​{1}}来完成工作。

我相信这种技术类似于+=的工作方式,除了它使用CRTP(我记得有一些关于它们使用它的讨论,因为一些较旧的编译器在没有它的情况下没有做正确的事情吗?)

这项技术的另一个有趣之处是boost::operators magically get + support

的子类

答案 2 :(得分:1)

我认为这应该是这样的:

//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被丢弃了。