我遇到了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;
}
错误是&#34;二进制&#39; ==&#39;:找不到运算符,它接受类型&#39; otherns :: Property&#39;的左手操作数。 (或者没有可接受的转换)&#34;这发生在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
来自第三方库的标题,因此我无法将操作符放入该标题中。
答案 0 :(得分:1)
规则相当复杂,而我自己并没有完全掌握它们,但让我们看看我们是否可以做出它们的正面或反面(我认为我们可以):
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{});
}
然后(2)在ns中隐藏(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
和
在std
内声明的任何运算符。你的帖子中已经有了这种情况。
正确的解决方案是:
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启动并从{{1}}带来(1)
,现在nx
被认为是(1)
。但(2)
比(1)
更专业,因此正确选择了(2)
。
如果您无法控制(1)
并且无法在那里添加运算符,那么我建议的是使用callables而不是依赖运算符。 E.g而不是namespace nx
使用std::find
和您自己的谓词(lambda),您可以在其中精确控制要调用的方法/运算符。当我说“确切”时,我指的是完全:即std::find_if
(或您声明的任何名称空间),而不是::operator==(x1, x2)
。
您可以阅读Herb Sutter撰写的这篇精彩文章Namespaces & Interface Principle
答案 1 :(得分:1)
只需在命名空间operator==
中声明otherns
(查找将在命名空间范围内找到它)
namespace otherns {
bool operator==(const otherns::Property &a, const otherns::Property &b) {
return a.getName() == b.getName();
}
}
您可以在第三方库的单独标题中执行此操作。
答案 2 :(得分:0)
您在全局命名空间中定义了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();
}
}
这个小改动使你的例子能够干净地编译。