我有一个模板类,我向其声明了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
一个),那么一切都编译得很好!
发生了什么事?
答案 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;
}
};
我们在这里使用ADL的魔力。继承plus_impl
表示MyClass<T> + int
在operator+
中找到plus_impl
。由于operator+
是模板运算符,它实际上将MyClass<T>
作为MyClass<T>
,而不是plus_impl
。 operator+
的实施然后使用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被丢弃了。