6

How can I use a complex number as the key in a map? Here is a small example which will not compile:

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

I can create the map without errors, but any method (e.g., the count call above as well as find, the [] operator, insert, etc.) generates compile-time errors. It is definitely a problem with my understanding, as I get similar results using clang and g++.

It looks like the compiler cannot compare two complex numbers. I created all of the comparison operators (e.g., bool operator< (const std::complex & lhs, const std::complex & rhs) {return (std::norm(lhs) < std::norm(rhs));}), which works for comparing complex numbers (as long as you don't mind 3 < -5 being true, which should be fine for map), but the compiler doesn't pick it up.

I have similar problems with unordered_map (there is no hash for complex<double>)

  • 4
    Do you not mind that in your comparison scheme `1 == -1 == i == -i` (and they all equal lots of other complex numbers)? You will only be able to insert one of them into the map. The same, of course, goes for every other equality set. – NPE Oct 07 '14 at 21:01
  • 2
    Welcome to StackOverflow. Which errors, specifically, do you get? – Ashalynd Oct 07 '14 at 21:02
  • 1
    "I created all of the comparison operators..." how about posting **all** your code? – Cornstalks Oct 07 '14 at 21:02
  • 2
    Consider instead using `(lhs.real() < rhs.real()) || (lhs.real() == rhs.real() && lhs.imag() < rhs.imag())`. – Timothy Shields Oct 07 '14 at 21:04
  • 2
    @WhozCraig There is no natural ordering on the complex numbers. Likewise, there are no built-in comparison operators (besides `==` and `!=`) for the `std::complex` type. – Timothy Shields Oct 07 '14 at 21:05
  • @TimothyShields well its a good thing `std::map` doesn't use either of those operators. Maybe my libstdc++ came with special sauce, but it seems to order them via `operator <` as I would expect. – WhozCraig Oct 07 '14 at 21:08
  • @WhozCraig The standard does not define a `<` operator for `std::complex`. libstdc++ may come with one, but it might be defined differently or simply not available with other providers. – Timothy Shields Oct 07 '14 at 21:12
  • 2
    @TimothyShields I switched into strict `std=c++11` and kerboom, no `operator <`. Thank you *very* much for pointing that out. Did not see that coming. Mucho thanks! – WhozCraig Oct 07 '14 at 21:25
  • 2
    Beware of using any form of `double` as a key, since rounding differences may keep you from retrieving the item later. – Mark Ransom Oct 07 '14 at 22:55
  • As an addition to what Mark said, beware of the fact that you could have NaNs in your complex. NaNs are going to break your comparison operator with respect to the strict weak ordering requirements. If it fits your requirements, and if you have some leeway, consider using an unordered map with a custom comparison operator that checks for NaNs instead. – bluescarni Oct 08 '14 at 10:58

3 Answers3

2

The problem with your approach is that, for an ordered container, such as std::map, the key must be amenable to a strict weak ordering. From wikipedia:

A strict weak ordering has the following properties. For all x and y in S,

For all x, it is not the case that x < x (irreflexivity).

For all x, y, if x < y then it is not the case that y < x (asymmetry).

For all x, y, and z, if x < y and y < z then x < z (transitivity).

For all x, y, and z, if x is incomparable with y, and y is incomparable with z, then x is incomparable with z (transitivity of incomparability).

Unfortunately, there is no natural way to impose a strict weak ordering on the complex numbers. For example, is 1 < i or i < 1? If you say that -i ≮ 1 and 1 ≮ i, then by the rules strict weak ordering it must be the true that -ii. That does mean sense.

You need to use complex numbers as a key, it is unlikely that you need and ordered container. Try looking into std::unordered_map.

From section 23.2.4.2 [associative.reqmts] of C++11 standard:

Each associative container is parameterized on Key and an ordering relation Compare that induces a strict weak ordering (25.4) on elements of Key. In addition, map and multimap associate an arbitrary type T with the Key. The object of type Compare is called the comparison object of a container.

ThomasMcLeod
  • 7,603
  • 4
  • 42
  • 80
  • @davidhigh, the internal algorithms of the container assume transitivity. Think of it as a pre-condition to constructing the container object. – ThomasMcLeod Oct 07 '14 at 23:22
  • standard lexicographic ordering -- though a bit useless in algebra -- fulfills the stated requirements. This is what is done by comparison of `std::array` as used in my answer. – davidhigh Oct 07 '14 at 23:25
  • @davidhigh, there is no logical or mathematical reason to favor the real line over the complex line, which is what your ordering does. For example, why should 1 + -10000 _i_ be greater the -1 + 10000 _i_? A possible order that might make sense in certain special cases is converting the complex number to polar form and then ordering on either the absolute value _or_ the argument (but not both). – ThomasMcLeod Oct 07 '14 at 23:41
  • 1
    any ordering in `IR x IR` favors something, and whether it is appropriate depends on the actual application [e.g. for real numbers my orderin is pefect, for imaginary it is probably somewhat inefficient]. But this is not an argument against using it. Some might be better for some key-data, others for other key-data, there's no free lunch. But actually, *any ordering will work*. – davidhigh Oct 07 '14 at 23:56
1

I have not looked at the implementation but per cppreference std::map uses std::less<T> as a comparison operator, that might not be specialized for std::complex, if you implement one pass it as the third parameter in your template std::map<std::complex, int, LessOperator> similarily with std:unordered_map, where you can supply a hash functor and an equality functor. If both of those are implemented you can use std::complex as the key.

Harald Scheirich
  • 9,676
  • 29
  • 53
  • If `std::less` doesn't have an explicit specialization (which it doesn't), then it will do `lhs < rhs` (which also isn't defined for `std::complex`). But you are correct about the solution being the third template argument. – Bill Lynch Oct 07 '14 at 22:44
  • I am curious now are you saying that if `std::less` is implemented then `lhs < rhs` is valid even if no `operator<()` is implemented for the given class ? – Harald Scheirich Oct 08 '14 at 12:09
  • Sorry for the confusion. The implementation provides [a default specialization](http://en.cppreference.com/w/cpp/utility/functional/less_void). So if there is no better specialization of `std::less`, like in this case and with `std::less`, then it defaults to the mentioned specialization. That specialization will attempt to do `lhs < rhs` which will fail. Basically, this is why the error message is that `std::complex` doesn't implement `lhs < rhs` rather than `std::less>` not existing. – Bill Lynch Oct 08 '14 at 12:46
1

The main answers have been pointed out in the comments above. The problem is that complex numbers are not ordered, and so there is no pre-defined smaller-operator for std::complex<T>. Nevertheless, you can define one for yourself, and this goes easily by using the already defined smaller operator for std::array<T,2>:

#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;
}

See live example here.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • 1
    You're not allowed to add your own things to namespace std – M.M Oct 07 '14 at 22:56
  • @Matt McNabb: you're right as `std::complex` is not a user-defined type. I've edited my answer by removing `namespace std`. – davidhigh Oct 07 '14 at 23:08
  • With respect to adding things to `namespace std` see [this answer](http://stackoverflow.com/questions/16122912/is-it-ok-to-specialize-stdnumeric-limitst-for-user-defined-number-like-class), which states that a specialization is possible only for user-defined types (which `std::complex` isn't). In [this blog](http://compgroups.net/comp.lang.c++/specializing-std-less/1015688), however, it is stated that although it is undefined behaviour modern compilers allow to do it (unless this specialization already exists) ... which is why it worked here. – davidhigh Oct 08 '14 at 07:42