4

It works for the struct xy that I declared. Why doesn't the same pattern work for complex<int>?

#include <complex>
#include <set>
using namespace std;

struct xy {
    int x, y;
};

bool operator< (const xy &a, const xy &b) {
    return a.x < b.x;
}

bool operator< (const complex<int> &a, const complex<int> &b) {
    return a.real() < b.real();
}

int main() {
    xy q;
    set<xy> s;
    s.insert(q);

    complex<int> p;
    set< complex<int> > t;   //If I comment out these two lines,
    t.insert(p);             //it compiles fine.

    return 0;
}

The error message:

In file included from c:\m\lib\gcc\mingw32\4.8.1\include\c++\string:48:0,
                 from c:\m\lib\gcc\mingw32\4.8.1\include\c++\bits\locale_classes.h:40,
                 from c:\m\lib\gcc\mingw32\4.8.1\include\c++\bits\ios_base.h:41,
                 from c:\m\lib\gcc\mingw32\4.8.1\include\c++\ios:42,
                 from c:\m\lib\gcc\mingw32\4.8.1\include\c++\istream:38,
                 from c:\m\lib\gcc\mingw32\4.8.1\include\c++\sstream:38,
                 from c:\m\lib\gcc\mingw32\4.8.1\include\c++\complex:45,
                 from test.cpp:1:
c:\m\lib\gcc\mingw32\4.8.1\include\c++\bits\stl_function.h: In instantiation of 'bool less<>::operator()(const _Tp&, const _Tp&) const':
c:\m\lib\gcc\mingw32\4.8.1\include\c++\bits\stl_tree.h:1321:11:   required from 'pair<> _Rb_tree<>::_M_get_insert_unique_pos(const key_type&)'
c:\m\lib\gcc\mingw32\4.8.1\include\c++\bits\stl_tree.h:1374:47:   required from 'pair<> _Rb_tree<>::_M_insert_unique(_Arg&&)'
c:\m\lib\gcc\mingw32\4.8.1\include\c++\bits\stl_set.h:463:29:   required from 'pair<> __cxx1998::set<>::insert(const value_type&)'
c:\m\lib\gcc\mingw32\4.8.1\include\c++\debug\set.h:220:59:   required from 'pair<> __debug::set<>::insert(const value_type&)'
test.cpp:28:19:   required from here
c:\m\lib\gcc\mingw32\4.8.1\include\c++\bits\stl_function.h:235:20: 
error: no match for 'operator<' (operand types are 'const std::complex<int>' and 'const std::complex<int>')
       { return __x < __y; }

My best guess is that this has something to do with complex<T> being a class, not a struct. By I can't see the logic of why that should make a difference. Or is it some template horribleness?

What I see happening is that the STL at some point tries (roughly speaking) to do a < b, where a and b are complex<int> instances. So it's looking for bool operator< (const complex<int> &a, const complex<int> &b). Well, there is exactly that declared just above main(). Why is it being unreasonable? I thought maybe it didn't like them being references. But removing the ampersands made no difference to its complaint.

Evgeni Sergeev
  • 22,495
  • 17
  • 107
  • 124
  • I believe it has to do with " point of definition" and " point of instantiation". I'm just unable to recall all the intricacies. Please have a look at them. – Nawaz Apr 09 '16 at 05:26
  • @Nawaz Do you mean the rule that we have to declare a function in the text of the program *before* we use it? (After the preprocessor preprocesses all the `#include`s.) That seems to make sense, but then I don't understand why the case of `struct xy` works... Perhaps the operators like `operator<` are always implicitly declared? – Evgeni Sergeev Apr 11 '16 at 11:49

3 Answers3

3

One option is to write a custom comparison functor, and instantiate the set with this.

#include <complex>
#include <set>    

bool operator< (const std::complex<int> &a, const std::complex<int> &b) {
    return a.real() < b.real();
}

struct my_less {
    bool operator() (const std::complex<int>& lhs, const std::complex<int>& rhs) const{
        return lhs < rhs;
    }
};

int main() {
    std::complex<int> p;
    std::set< std::complex<int>, my_less > t;
    t.insert(p);

    return 0;
}

Sample on Coliru

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
  • 2
    it's UB to add `operator<` for `std::complex` inside `namespace std` – TemplateRex Apr 09 '16 at 08:20
  • @TemplateRex Ok, true, I'll reduce it to just the functor. – Dan Mašek Apr 09 '16 at 08:23
  • 1
    A more general rule I found is that adding anything at all to `namespace std` is Undefined Behaviour, with some limited exceptions. See [this answer](http://stackoverflow.com/q/25120423/1143274), which quotes the relevant paragraph from the standard. – Evgeni Sergeev Apr 11 '16 at 11:14
3

It works for the struct xy that I declared. Why doesn't the same pattern work for complex<int>?

The reason is that when you use types from namespace std only, like std::set and std::complex, the compiler has no reason to look for operators in any other namespaces.

With struct xy this is different, as the operator is declared together with the type.


And also, the current standard says:

The effect of instantiating the template complex for any type other than float, double, or long double is unspecified.

So using complex<int> might, or might not, work depending on which compiler you use. "Unspecified" is not as bad as "undefined", but of course not very reliable or portable.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • *"the compiler has no reason to look for operators in any other namespaces."* This is wrong. The compiler can, does, look for names in other namespaces as well, especially in the global one. It all depends on the point-of-definition (and possibly, point of instantiation). – Nawaz Apr 09 '16 at 10:23
  • As of today I face this issue, so I agree that it's an unspecified behaviour. Have noted 1 workaround in [my answer](https://stackoverflow.com/a/63686831/514235). This might help for the non-std namespaces at least. @Nawaz – iammilind Sep 01 '20 at 11:34
  • ah, yes. That is how ADL works. It works in the namespaces in which types are originally defined. But @iammilind, your comment *"so I agree that it's an unspecified behaviour"* is not correct, as we are referring to the lookup rules (which is pretty much well-defined). Unspecified is different thing here, not the actual problem though. – Nawaz Sep 01 '20 at 19:25
  • @Nawaz, may be you are right. But I feel it's unspecified because a global `operator==` works for a namespaced class and `operator<` doesn't! – iammilind Sep 02 '20 at 04:38
  • That might be a bug? Also, such things are not likely to be left unspecified. – Nawaz Sep 02 '20 at 10:11
0

As of today, I am facing this issue with my Ubuntu's g++. Suppose I have:

namespace X { namespace Y { class C { ... }; } }

Now operator== is recognized if it's defined in a global namespace, such as:

bool operator== (const X::Y::C& lhs, const X::Y::C& rhs) { return lhs == rhs; }

However, somehow compiler doesn't recognize operator<, if defined in the same way.

Now following way works well:

namespace X { namespace Y { 
     bool operator< (const C& lhs, const C& rhs) { return lhs < rhs; } } }

However, whether you should do it the same with expanding standard namespace std is a controversial topic. :-)

iammilind
  • 68,093
  • 33
  • 169
  • 336