模板的好友运算符 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);
注意错误指的是friend
operator+
方法
但是,如果我删除第一个 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 类型中删除了 const
。 const
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;
}
};
我们在这里使用 ADL 的魔力。从 plus_impl
继承意味着 MyClass<T> + int
在 plus_impl
中找到 operator+
。由于 operator+
是模板运算符,它实际上将 MyClass<T>
作为 MyClass<T>
而不是 plus_impl
。 operator+
的实现然后使用 MyClass<T>
的 +=
来完成工作。
我相信这种技术类似于 boost::operators
的工作方式,除了它使用 CRTP(我记得一些关于他们使用它的讨论,因为一些旧的编译器没有它就不能做正确的事情?)
这项技术的另一个有趣之处在于 MyClass<T>
magically get +
support.
的子classes
我有一个模板 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);
注意错误指的是friend
operator+
方法
但是,如果我删除第一个 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 类型中删除了 const
。 const
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;
}
};
我们在这里使用 ADL 的魔力。从 plus_impl
继承意味着 MyClass<T> + int
在 plus_impl
中找到 operator+
。由于 operator+
是模板运算符,它实际上将 MyClass<T>
作为 MyClass<T>
而不是 plus_impl
。 operator+
的实现然后使用 MyClass<T>
的 +=
来完成工作。
我相信这种技术类似于 boost::operators
的工作方式,除了它使用 CRTP(我记得一些关于他们使用它的讨论,因为一些旧的编译器没有它就不能做正确的事情?)
这项技术的另一个有趣之处在于 MyClass<T>
magically get +
support.