【问题标题】:find() using overloaded operator==find() 使用重载的 operator==
【发布时间】:2013-10-04 17:57:27
【问题描述】:

我尝试使用重载运算符==() 在向量中查找元素。但是,如果在以下代码中使用type1,则输出为 1 和 0(未找到)。使用type2 给出1 和1。环境是Xubuntu 12.04 和g++ 版本4.6.3。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

typedef pair<string, int> type1;
struct type2: public type1 {};
#define TYPE type1

bool operator== (const TYPE& lhs, const TYPE& rhs) {
    return lhs.first == rhs.first;
}

int main()
{
    vector<TYPE> vec;
    TYPE v1, v2;

    v1.first = "abc"; v1.second = 1; vec.push_back(v1);
    v2.first = "abc"; v2.second = 2;

    cout << (v1 == v2) << endl;
    cout << (find(vec.begin(), vec.end(), v2) != vec.end()) << endl;
}

【问题讨论】:

    标签: c++ operator-overloading overload-resolution argument-dependent-lookup name-lookup


    【解决方案1】:

    std::pair 在命名空间std 中有其默认的operator==,这是任意对的模板,比较firstsecond 字段。此运算符在四种情况之一中被选中,即findTYPE == type1。细节有点复杂:

    TYPE == type1 实际发生的情况是(如果我错了,请纠正我)

    • for v1 == v2 ADL (Argument Dependent Name Lookup) 用于在std 中查找operator==,这意味着该运算符被添加到正常重载集中。但是,当前翻译单元中的非模板版本仍然优于来自std 的模板operator==
    • std::find 调用在std 中实例化,因此operator== 的查找直接在std 中开始。它找到一个匹配项(不使用 ADL!),因此不会搜索包含 OP 自己的运算符的封闭范围。

    对于TYPE == type2

    • v1 == v2 很简单——它直接在封闭的命名空间中找到operator==
    • std::find 也在 std 中实例化,但来自主作用域的自定义运算符使用 ADL 添加到重载解析集,然后发现比 std 中的更具体。

    【讨论】:

    • 关于命名空间的要点。但最重要的是pair 还是find 的命名空间?
    • 据我所知,find 在当前翻译单元中被实例化,然后在 std::pair 上使用 ADL 解析 operator==
    • @Nawaz,那为什么v1 == v2 在这两种情况下都返回true
    • @MarkRansom:你是对的。我有点困惑。而且我认为这个答案不能解释这种行为,所以我删除了我的赞成票,然后因为不完整而被反对。
    • 好点 - 一个可能的起点可能是 operator== 通过 ADL 找到正常的 std::pair 运算符,但仍然采用新的运算符,因为非模板优于非模板。仍然没有通过find 版本解释失败。
    【解决方案2】:

    @AlexanderGessler 的回答在几个细节上是不完整的。那么让我们为两种表达式和两种类型都运行编译器,好吗?

    表达式 1

    cout &lt;&lt; (v1 == v2) &lt;&lt; endl;

    首先,对于type1type2非限定名称查找main() 函数范围向外开始,并在全局范围内找到您自己的operator== 函数。

    第二,依赖于参数的名称查找(ADL)从namespace std 中找到std::pair 的函数模板operator==。实际上,ADL 找到了更多 std::operator== 函数模板(来自 std::vectorstd::string,因为您也包含了这些标题)。

    注意:ADL 还会找到 type2 的匹配项,因为它的基类 type1 会将 namespace std 添加到其关联命名空间的集合中。


    3.4.2 依赖于参数的名称查找 [basic.lookup.argdep]

    ——如果 T 是一个类类型(包括联合),它的关联类是: 班级本身;它所属的类别(如有的话);及其 直接和间接基类。其关联的命名空间是 与其关联的类是其成员的命名空间。


    第三,模板参数推导对所有找到的函数模板进行。对于type1,只有std::pair 的函数模板才能在参数推导中保留下来(并且它将其模板参数分别推导出为std::stringint)。但是,对于type2,没有适合的模板参数集,因为type2 不是std::pair 模板的实例化。

    第四,重载分辨率发挥作用。对于type1,您自己的函数operator==std::operator== 函数模板的等级相同(精确匹配)。因此,抢七将选择您的非模板函数。对于type2,只有一个可行的函数,因此重载决议不会发挥作用,您的函数将被选中。

    结论 1type1type2 将给出相同的答案(选择了您的版本),尽管原因不同。

    表达式 2

    cout &lt;&lt; (find(vec.begin(), vec.end(), v2) != vec.end()) &lt;&lt; endl;

    这里我们需要先解决对find的调用。由于您的using namespace std;,非限定名称查找已经找到(没有双关语)std::find,但即使没有 using 指令,std::vector 迭代器上的 ADL 也会找到它。它会将std::find 的第三个模板参数推导出为type1type2

    std::find 内,找到了对operator== 的调用。同样,将首先执行普通查找。但是,这发生在namespace std 内部。它将找到几个operator== 函数模板(用于std::vectorstd::stringstd::pair)。一旦在不合格的名称查找过程中找到一个范围内的候选对象,名称查找的这个阶段就会停止。

    但是,ADL 仍在执行中。请注意,尽管全局命名空间不是type1 的关联命名空间因为它只是namespace std 中的类的类型定义。所以对于type1,ADL 没有发现任何新东西。相反,type2 确实将全局命名空间作为其关联的命名空间,因此 ADL 将在这种情况下找到您的 operator== 函数模板。

    对于type1,template-argument-deduction 查找std::stringint 作为operator== 函数模板std::pair 的模板参数。对于type2,同样没有适合的模板参数集,因为type2 不是std::pair 模板的实例化。

    剩下的就是重载决议。对于type1,只有一个可行的函数(std::operator== 模板的实例),重载决议不起作用。对于type2,也只有一个可行的函数(可行是因为它只需要标准的derived-to-base 转换)。因此,重载决议也不会发挥作用。

    结论 2:对于type1std 版本)和type2(您的版本),您会得到不同的结果。

    总结

    仅仅因为这些事情在不同命名空间中的多个重载会变得非常棘手,这里有一个包含三位一体的汇总表(名称查找、参数推导和重载解析)。对于每个阶段和每种类型,我都列出了该阶段之后幸存的候选人。底行显示被调用的函数。

    表达式 1

    +---------------------+-----------------+-----------------+
    | phase               | type1           | type2           |
    +---------------------+-----------------+-----------------+
    | unqualified lookup  |    ::operator== |    ::operator== |
    | ADL                 | std::operator== | std::operator== |
    +---------------------+-----------------+-----------------+
    | argument deduction  |    ::operator== |    ::operator== |
    |                     | std::operator== |                 |
    +---------------------+-----------------+-----------------+
    | overload resolution |    ::operator== |    ::operator== |
    +---------------------+-----------------+-----------------+
    

    表达式 2

    +---------------------+-----------------+-----------------+
    | phase               | type1           | type2           |
    +---------------------+-----------------+-----------------+
    | unqualified lookup  | std::operator== | std::operator== |
    | ADL                 |                 |    ::operator== |
    +---------------------+-----------------+-----------------+
    | argument deduction  | std::operator== |    ::operator== |
    +---------------------+-----------------+-----------------+
    | overload resolution | std::operator== |    ::operator== |
    +---------------------+-----------------+-----------------+
    

    请注意,非限定查找会根据其开始的范围(全局范围内的函数范围与命名空间范围)找到不同的名称,并且 ADL 类似地根据被认为关联的命名空间找到不同的名称(namespace std 与全局命名空间)。

    【讨论】:

    • 花了一些时间才明白。感谢您的详细解释。
    • 很高兴能帮上忙!
    【解决方案3】:

    std::pair 有自己的operator==,优先于您自己的。

    【讨论】:

    • 感谢您的回复和链接。
    【解决方案4】:

    我认为你最好使用find_if 而不是 find。它需要一个谓词,因此您可以将比较器定义为普通函数/函子并传递它。

    【讨论】:

    • 谢谢。将尝试使用函子作为谓词。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-13
    • 2021-03-16
    相关资源
    最近更新 更多