【问题标题】:unexpected error in unordered_map iterator comparisonunordered_map 迭代器比较中的意外错误
【发布时间】:2022-01-03 20:43:12
【问题描述】:

我有一个简单的地图 std::map,我使用的是 c++11,所以出于性能原因,我将其替换为 unordered_map。将迭代器与 end() 进行比较时出现以下错误。

auto cit = str_map_.find(str);
if (cit != str_map_.end()) {
   ...
}

在 'bool my_namespace::operator!=(const T1&, const T2&) 的实例化中 [with T1 = std::__detail::_Node_iterator<:pair std::__cxx11::basic_string my_namespace::>; T2 = std::__detail::_Node_iterator<:pair std::__cxx11::basic_string my_namespace::mytype false true>]':没有匹配的函数...

我将它调试到我相当有创意的比较运算符 my_namespace::MyType:

template <class T>
struct MyType {
    T* mt_;
};

struct MyTempClass {
    std::string mtc_;

    static int Compare(MyType<MyTempClass> const& lhs, MyType<MyTempClass> const& rhs) {
        return lhs.mt_->mtc_.compare(rhs.mt_->mtc_);
    }

    static int Compare(std::string const& lhs, MyType<MyTempClass> const& rhs) {
        return lhs.compare(rhs.mt_->mtc_);
    }

    static int Compare(MyType<MyTempClass> const& lhs, std::string const& rhs) {
        return lhs.mt_->mtc_.compare(rhs);
    }
};

template <class T1, class T2>
bool operator !=(T1 const& lhs, T2 const& rhs) {
    int res = MyTempClass::Compare(lhs, rhs);
    return (res != 0);
}

template <class T1, class T2>
bool operator ==(T1 const& lhs, T2 const& rhs) {
    int res = MyTempClass::Compare(lhs, rhs);
    return (res != 0);
}

static std::unordered_map<std::string, MyType<MyTempClass>> my_map;

但我仍然不明白为什么会发生这种情况:相同的代码在普通地图上也能正常工作,而值类型不应该参与迭代器比较?

【问题讨论】:

    标签: c++ unordered-map


    【解决方案1】:

    您定义了一个将任何类型作为参数的operator!= 重载。该重载与MyType 类型位于同一命名空间中。因此,可以通过 ADL 找到它。

    如错误消息所示,标准库使用的 std::unordered_map 迭代器是类模板特化,专门针对 std::unordered_map 模板参数。因此,当您将迭代器与!= 进行比较时,将对参数执行 ADL,并且 ADL 搜索的命名空间还包括参数类型的类型模板参数的命名空间。因此,您在MyType 命名空间中的operator!= 重载也会被找到并参与重载解析。

    假设您使用 libstdc++ 作为基于错误消息的标准库实现,您可以查看 it's implementationoperator!= 的哈希表迭代器,您将看到它使用这些迭代器的基类,并为基类对象的引用定义比较运算符。

    因此,迭代器比较的标准重载需要在其参数中进行派生到基引用的转换,而您的重载则不需要。

    因此,您的重载更好,将被选择进行cit != str_map_.end() 比较。您的重载尝试传递参数 MyTempClass::Compare 显然不起作用,因为这些函数不期望 std::unordered_map 迭代器。


    解决方案不是为不依赖于用户定义类型的类型对重载运算符。将重载限制为您自己的类型:

    template <class T1, class T2>
    bool operator !=(MyType<T1> const& lhs, T2 const& rhs) {
        int res = MyTempClass::Compare(lhs, rhs);
        return (res != 0);
    }
    
    template <class T1, class T2>
    bool operator !=(T1 const& lhs, MyType<T2> const& rhs) {
        int res = MyTempClass::Compare(lhs, rhs);
        return (res != 0);
    }
    

    (相当于operator==)。


    据我所知,不禁止重载标准库类型对的运算符,但我也不认为标准库需要解决在您的代码中产生的冲突。

    使用std::map,标准库实现可能选择了一种不同的方式来实现迭代器比较,这使得它更适合重载解决方案,或者通过不使迭代器成为专门针对键的模板来避免 ADL 发现您的重载/值类型。

    【讨论】:

    • 谢谢,但我真的很惊讶值类型在迭代器比较中发挥了任何作用。
    • @zzz777 它没有。我的解释的简短版本是,您只是通过声明一个声称接受任何类型的 operator!= 重载来替换 != 的含义。如果在声明重载的同一命名空间中使用包含 != 的表达式,这一点会更加明显。在您的情况下,由于 C++ 参数相关的查找规则,它只是有点迂回。
    • 抱歉打扰了。我已经解决了这个问题,谢谢。但我仍然不明白,我没有为键覆盖任何东西,键仍然是简单的 std::string 不管它应该对地图没有影响的值是什么???假设我有根本无法比较的值或有一些不正常的顺序,我相信具有这些值的地图仍然可以工作?
    • @zzz777 键和值类型并不重要。您的自定义类型作为模板参数出现在其中很重要。比较 cit != str_map_.end() 比较的是迭代器,而不是键。比较应该选择标准库中为它们定义的operator!= 重载。但是正常的重载决议适用于操作符的每次使用。 C++ 重载解析具有 argumentdependent lookup 规则 (ADL),该规则表示重载通常不会通过直接名称查找找到,而是在与参数类型关联的命名空间中定义。跨度>
    • 以这种方式发现您的重载(答案中的详细信息)并且恰好更适合重载解决方案。而不是使用标准库定义的operator!= 重载应该 用于迭代器,而是调用您的函数,这显然是无稽之谈。比较标准库中将您的自定义类型作为模板参数的任何类型都可能发生这种情况。它并不特定于 std::unordered_map 或其迭代器或任何东西。
    猜你喜欢
    • 2014-06-30
    • 2014-03-13
    • 2015-05-13
    • 2018-12-17
    • 2018-05-31
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    • 2012-03-28
    相关资源
    最近更新 更多