【问题标题】:Using std::complex<double> as a std::map key使用 std::complex<double> 作为 std::map 键
【发布时间】:2014-10-07 20:58:32
【问题描述】:

如何在地图中使用复数作为键?这是一个无法编译的小例子:

#include <complex>
#include <map>
int main() {
  std::complex<double> zero = 0.0;
  std::map<std::complex<double>, int> theMap;
  return (theMap.count(zero));
}

我可以毫无错误地创建地图,但是任何方法(例如,上面的 count 调用以及 find[] 运算符、insert 等)都会产生编译时错误。这绝对是我理解的问题,因为我使用 clang 和 g++ 得到了类似的结果。

编译器似乎无法比较两个复数。我创建了所有比较运算符(例如,bool operator&lt; (const std::complex &amp; lhs, const std::complex &amp; rhs) {return (std::norm(lhs) &lt; std::norm(rhs));}),它适用于比较复数(只要您不介意3 &lt; -5 为真,这对于map 应该没问题),但编译器不不要捡起来。

我有类似的 unordered_map 问题(complex&lt;double&gt; 没有哈希)

【问题讨论】:

  • 您不介意在您的比较方案中1 == -1 == i == -i(它们都等于许多其他复数)吗?您只能将其中一个插入地图。当然,其他所有等式集也是如此。
  • 欢迎来到 StackOverflow。具体来说,您遇到了哪些错误?
  • 考虑改用(lhs.real() &lt; rhs.real()) || (lhs.real() == rhs.real() &amp;&amp; lhs.imag() &lt; rhs.imag())
  • @WhozCraig 复数没有自然排序。同样,std::complex 类型没有内置的比较运算符(除了==!=)。
  • @TimothyShields 我切换到严格的std=c++11 和kerboom,没有operator &lt;。非常感谢您非常指出这一点。没有看到那到来。非常感谢!

标签: c++ c++11 stdmap


【解决方案1】:

我没有查看实现,但每个 cppreference std::map 使用 std::less&lt;T&gt; 作为比较运算符,如果您实现一个传递它作为模板中的第三个参数 @987654327 @ 与 std:unordered_map 类似,您可以在其中提供哈希函子和相等函子。如果这两个都实现了,您可以使用std::complex 作为键。

【讨论】:

  • 如果std::less&lt;T&gt; 没有明确的特化(它没有),那么它将执行lhs &lt; rhs(也没有为std::complex&lt;T&gt; 定义)。但是您对作为第三个模板参数的解决方案是正确的。
  • 我现在很好奇你是说如果std::less&lt;T&gt; 被实现了,那么lhs &lt; rhs 是有效的,即使没有为给定的类实现operator&lt;()
  • 很抱歉给您带来了困惑。该实现提供a default specialization。因此,如果没有更好的 std::less 特化,就像在这种情况下和 std::less&lt;int&gt; 一样,那么它默认为提到的特化。该专业化将尝试执行lhs &lt; rhs,这将失败。基本上,这就是为什么错误消息是 std::complex&lt;double&gt; 没有实现 lhs &lt; rhs 而不是 std::less&lt;std::complex&lt;double&gt;&gt; 不存在。
【解决方案2】:

主要答案已在上面的 cmets 中指出。问题是复数没有排序,因此std::complex&lt;T&gt; 没有预定义的较小运算符。不过,您可以为自己定义一个,这很容易通过使用已经为 std::array&lt;T,2&gt; 定义的较小的运算符来实现:

#include <iostream>
#include<complex>
#include<map>
#include<unordered_map>
#include<array>

template<typename T> struct less {};

    template<typename T>
    struct less<std::complex<T> >
    {
        bool operator()(std::complex<T> const& a, std::complex<T> const& b)
        {
            return std::array<T,2>{a.real(),a.imag()} < std::array<T,2>{b.real(),b.imag()};
        }
    };

int main()
{
    std::map<std::complex<double>, int, less<std::complex<double> > > m;

    m[std::complex<double>(1.0,0.0)]=1;
    m[std::complex<double>(0.0,1.0)]=2;
    m[{0.5,0.5}]=3;

    return 0;
}

查看实时示例here

【讨论】:

  • 你不能将自己的东西添加到命名空间 std
  • @Matt McNabb:你是对的,因为std::complex 不是用户定义的类型。我已通过删除 namespace std 来编辑我的答案。
  • 关于向namespace std 添加内容,请参阅this answer,它指出只有用户定义的类型才能进行特化(std::complex 不行)。然而,在this blog 中,虽然它是未定义的行为,但现代编译器允许这样做(除非这种专门化已经存在)......这就是它在这里工作的原因。
【解决方案3】:

您的方法的问题在于,对于有序容器,例如std::map,密钥必须符合严格的弱排序。来自wikipedia

严格的弱排序具有以下属性。对于 S 中的所有 x 和 y,

对于所有的 x,不是 x

对于所有 x, y,如果 x

对于所有 x、y 和 z,如果 x

对于所有x、y、z,如果x与y不可比,y与z不可比,则x与z不可比(不可比传递性)。

不幸的是,没有自然的方法可以对复数施加严格的弱排序。例如,是 1 i 还是 i i ≮ 1 和 1 ≮ i,那么根据严格的弱排序规则,-i。这确实意味着意义。

您需要使用复数作为键,您不太可能需要订购容器。试试看std::unordered_map

来自 C++11 标准的第 23.2.4.2 [associative.reqmts] 节:

每个关联容器都在 Key 和一个排序关系 Compare 上进行参数化,该排序关系对 Key 的元素产生严格的弱排序 (25.4)。此外,map 和 multimap 将任意类型 T 与 Key 相关联。 Compare 类型的对象称为容器的比较对象。

【讨论】:

  • @davidhigh,容器的内部算法假设传递性。将其视为构造容器对象的前提条件。
  • 标准字典顺序——尽管在代数中有点用处——满足了规定的要求。这是通过比较我的答案中使用的std::array&lt;T,2&gt; 来完成的。
  • @davidhigh,没有逻辑或数学上的理由来支持实线而不是复杂线,这就是您的订购所要做的。例如,为什么 1 + -10000 i 应该大于 -1 + 10000 i?在某些特殊情况下可能有意义的可能顺序是将复数转换为极坐标形式,然后对绝对值 参数(但不是两者)进行排序。
  • IR x IR 中的任何排序都有利于某些东西,是否合适取决于实际应用 [e.g.对于实数,我的 orderin 是完美的,对于想象的它可能有点低效]。但这并不是反对使用它的理由。有些对一些关键数据可能更好,有些对其他关键数据可能更好,没有免费的午餐。但实际上,任何排序都会起作用
猜你喜欢
  • 1970-01-01
  • 2014-08-06
  • 2011-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
  • 2012-10-04
相关资源
最近更新 更多