如何在不同的命名空间中指定重载运算符

How can I specify an overloaded operator in a different namespace?

本文关键字:重载 运算符 命名空间      更新时间:2023-10-16

我在使用C++标准库时遇到问题。下面的例子没有编译:(注意,这是为了制作一个最小的例子,所以没有多大意义)

#include <algorithm>
#include <string>
#include <vector>
namespace otherns {
class Property {
public:
const std::string &getName() const { return m_name; }
private:
std::string m_name;
};
}
bool operator==(const otherns::Property &a, const otherns::Property &b) {
return a.getName() == b.getName();
}
/* Merge, second takes priority */
std::vector<otherns::Property>
merge_props(const std::vector<otherns::Property> &xs,
const std::vector<otherns::Property> &ys) {
std::vector<otherns::Property> ans = ys;
for (const auto &x : xs) {
if (std::find(ans.begin(), ans.end(), x) == ans.end()) {
ans.push_back(x);
}
}
return ans;
}

错误为"binary'==':找不到接受类型为'otherns::Property'的左侧操作数的运算符(或者没有可接受的转换)",该错误发生在std::find的实现中的某个位置。这是MSVC,但我也尝试过clang和gcc,结果类似。

以下代码确实有效:

std::vector<otherns::Property>
merge_props(const std::vector<otherns::Property> &xs,
const std::vector<otherns::Property> &ys) {
std::vector<otherns::Property> ans = ys;
for (const auto &x : xs) {
if (std::find_if(ans.begin(), ans.end(), [&x](const otherns::Property &y) {
return x == y;
}) == ans.end()) {
ans.push_back(x);
}
}
return ans;
}

我想这与ADL/Koenig查找有关,但我真的不明白为什么找不到我的operator==。如果我想使用find函数的第一种更简单的形式,最好的解决方案是什么?

事实上,otherns来自第三方库的标头,所以我不能将运算符放入该标头中。

规则非常复杂,我自己也没有完全掌握,但让我们看看我们是否能理解它们(我认为我们可以):

namespace nx {
struct X {};
}
namespace ns {    
auto foo(nx::X x1, nx::X x2) { return x1 == x2; }
// error: no match for 'operator==' (operand types are 'nx::X' and 'nx::X')
}
auto operator==(nx::X, nx::X) { return true; }
auto global_foo()
{
return ns::foo(nx::X{}, nx::X{}); 
}

找不到这一点的原因很简单:operator==在使用之前没有声明。ADL还没有什么可做的。到目前为止还不错。我们理解这一点。让我们修复它:

namespace nx {
struct X {};
}
auto operator==(nx::X, nx::X) { return true; }
namespace ns {
auto foo(nx::X x1, nx::X x2) { return x1 == x2; }
}
auto global_foo()
{
return ns::foo(nx::X{}, nx::X{}); 
}

这行得通吗?是的,它会编译并调用我们的operator==。这是正确的解决方案吗。因为如果我们加上这个:

namespace nx {
struct X {};
}
auto operator==(nx::X, nx::X) { return true; } // (1)
namespace ns {
template <class T> auto operator==(T, int) { return false; } // (2)
auto foo(nx::X x1, nx::X x2) { return x1 == x2; }
// error: no match for 'operator==' (operand types are 'nx::X' and 'nx::X')
}
auto global_foo()
{
return ns::foo(nx::X{}, nx::X{});
}

然后,ns中的(2)将(1)隐藏在全局命名空间中,即使(1)更适合。这被称为名称隐藏,并且——再次——以任何方式都不涉及ADL。

更糟糕的是:

namespace nx {
struct X {};
}
auto operator==(nx::X, nx::X) { return true; } // (1)
namespace ns {
template <class T> auto operator==(T, T) { return false; } // (2)
auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // calls (2)
}
auto global_foo()
{
return ns::foo(nx::X{}, nx::X{}); 
}

将编译并静默地调用(2),而不是我们的运算符(1)

对于真实世界的上下文,请将namespace ns想象为命名空间std在CCD_ 11中声明的任何运算符。你的岗位上有这种情况。

正确的解决方案是:

namespace nx {
struct X {};
auto operator==(nx::X, nx::X) { return true; } // (1)
}
namespace ns {
template <class T> auto operator==(T, T) { return false; } // (2)
auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // calls (1)
}
auto global_foo()
{
return ns::foo(nx::X{}, nx::X{}); 
}

这里发生的是,ADL开始从nx引入(1),现在(1)被认为与(2)并列。但是CCD_ 16比CCD_ 17更专业,因此CCD_。

如果你不能控制namespace nx,也不能在那里添加运算符,那么我可以建议你使用可调用函数,而不是依赖运算符。例如,使用std::find_if来代替std::find和您自己的谓词(lambda),在这里您可以精确地控制要调用的方法/运算符。当我说"确切地"时,我的意思是确切地:即::operator==(x1, x2)(或您声明的任何命名空间)而不是x1 == x2


您可以阅读Herb Sutter Namespaces&接口原理

只需在名称空间otherns中声明operator==(查找将在名称空间范围内找到它)

namespace otherns {
bool operator==(const otherns::Property &a, const otherns::Property &b) {
return a.getName() == b.getName();
}
}

工作代码

您可以在第三方库的单独标头中执行此操作。

您在全局命名空间中定义了operator==(可能被误认误导了)。通过依赖于参数的查找不会在那里找到它。

运算符应该在与其参数(其中一个)相同的命名空间中声明:

namespace otherns {
class Property {
public:
const std::string &getName() const { return m_name; }
private:
std::string m_name;
};
bool operator==(const otherns::Property &a, const otherns::Property &b) {
return a.getName() == b.getName();
}
}

这个小小的更改使您的示例能够干净地编译。