14

This question is inspired by Issue with std::reference_wrapper. Let' say, for example, operator< for std::vector. It's defined as a function template as

template< class T, class Alloc >
bool operator<( const vector<T,Alloc>& lhs,
                const vector<T,Alloc>& rhs );

As a result, implicit conversion of function argument to the type of the corresponding function parameter is denied (basically because of its template nature). This greatly reduces the usefulness and convenience of std::reference_wrapper. For example, you cannot use std::sort on std::vector<std::reference_wrapper<std::vector<int>>>.

On the other hand, all the problems are solved only if operator< is defined as a non-template Koenig operator like

template <...>
class vector ... {
  friend bool operator<(const vector& a, const vector& b) {...}
};

I'm wondering why the standard library has adopted the former approach instead of this?

Community
  • 1
  • 1
Lingxi
  • 14,058
  • 1
  • 35
  • 85
  • @jxh The crucial difference between the two is: one is a template, effectively forbidding implicit conversion, while the other is not, allowing implicit conversion. – Lingxi May 15 '15 at 17:47
  • 4
    Well, at the time that `operator – T.C. May 15 '15 at 17:52
  • 2
    About the wording: there are no Koenig operators. There are, instead, functions that are found by ADL/Koenig lookup. – edmz May 15 '15 at 17:53
  • @black I just need a convenient word for the `friend bool operator – Lingxi May 15 '15 at 17:56
  • The friend function is implicitly inline. – dyp May 15 '15 at 18:00
  • @dyp Is there any problem with that? – Lingxi May 15 '15 at 18:02
  • 1
    It's not a problem, but I think at the time when the SGI STL was invented, it still was relevant to optimizers. -- Also note that this function doesn't need to be a friend (doesn't need privileged access), so maybe it was thought to be a cleaner solution. – dyp May 15 '15 at 18:03
  • 2
    Maybe a better way to allow this btw would be to add wrapper operators to `std::reference_wrapper`, just like its `operator()`. – dyp May 15 '15 at 18:05
  • 1
    Here's a question: suppose you wanted to overload operator< for std::vector. Would it be doable given your suggestion? It seems like it might be impossible. Your function would be an exact, non templated match, so defining your own operator< would result in an ambiguity error. Whereas in the current form, overloading operator< will work perfectly since it takes precedence over a template function. – Nir Friedman May 15 '15 at 18:13
  • @NirFriedman Would `using some_ns::operator – Lingxi May 15 '15 at 18:18
  • @dyp Very nice suggestion! – Lingxi May 15 '15 at 18:20
  • This is not a solution, because the pre existing operator< would always be around, because it's namespace gets dragged in with the type (vector) during adl lookup. So you will get an ambiguous function call error. I think this is a reasonable explanation, so I'll post it. – Nir Friedman May 15 '15 at 19:29
  • Is this not exactly http://stackoverflow.com/questions/30265207/should-operators-be-declared-as-non-member-non-template-friends – Barry May 15 '15 at 20:59
  • It seems like both questions were asked almost simultaneously. Since this question has received more upvotes and has an answer, the most reasonable thing seems to be to close the other. – Nir Friedman May 15 '15 at 21:08

1 Answers1

1

Consider this code (A.h):

template <class T>
class A {
  public:
  T m_x;

  friend bool operator<(const A & lhs, const A & rhs) {
    return lhs.m_x < rhs.m_x;
  }
};

And main.cpp:

#include "A.h"

namespace buddy {
bool operator<(const A<double> & lhs, const A<double> &rhs) {
    return lhs.m_x > rhs.m_x;
};
}
using namespace buddy;
int main(int argc, char ** argv) {

  A<double> a1;
  A<double> a2;

  a1 < a2;

  return 0;
}

This code does not compile:

main.cpp:14:5: error: ambiguous overload for ‘operator<’ (operand types are ‘A’ and ‘A’) a1 < a2;

The reason of course is that both of the operator<'s are exact matches. On the other hand, if we change the first operator< to (defined outside the class):

template <class T>
bool operator<(const A<T> & lhs, const A<T> & rhs) {
  return lhs.m_x < rhs.m_x;
}

The compiler stops complaining: it's now a competition between an exact match, and a function template, so the exact match is used.

If operator< was defined in the fashion that you're suggesting, there would be no reasonable way for users of std::vector to redefine the behavior of operator<, short of specializing std::vector themselves, which is a lot more work.

In conclusion, the standard writers elected to make it easier to overload operator<, than to provide an operator< that might be more useful in certain situations. I think they made the right choice.

Nir Friedman
  • 16,242
  • 2
  • 39
  • 67
  • 1
    *"there would be no reasonable way for users of std::vector to redefine the behavior of operator – dyp May 15 '15 at 20:48
  • There's nothing particularly fundamental about operator< for a vector; it's just defined in some reasonable default way. If the default implementation doesn't make sense for you, and there's another operation that's always what you want, it makes sense to overload operator<. a="" be="" broad="" can="" code.="" concede="" consensus="" dangerous="" don="" easier="" even="" feel="" i="" if="" it="" lead="" make="" of="" opinion.="" or="" possible="" range="" should="" standard="" sure="" that="" the="" there="" things="" this="" to="" unclear="" unless="" way="" where="" will="" you=""> – Nir Friedman May 15 '15 at 21:02
  • Alexander Stepanov, one of the inventors of the SGI STL, would surely disagree: `operator – dyp May 15 '15 at 21:58
  • My point was that there is nothing fundamental about this particular operator<. a="" accept="" allowing="" and="" as="" authors="" be="" because="" both="" can="" clearly="" comparator.="" containers.="" could="" default="" defined="" defined.="" desired="" didn="" do="" even="" fast="" for="" generic="" generically="" has="" have="" intuitive="" is="" it="" just="" lookup="" makes="" mandatory="" necessary="" not="" once="" one="" operator="" out="" overloading="" own="" pass="" perfect="" pointed="" properties="" properties.="" reasonable="" see="" sense.="" sorting="" stl="" that="" the="" their="" there="" though="" trivially="" unordered_map="" which="" with="" yes="" you="" your=""> – Nir Friedman May 15 '15 at 22:32
  • *"Unordered_map/set do not have operator – dyp May 15 '15 at 22:44
  • I don't see any relevance of efficiency here. Many classes provide inefficient operators. If you want another example, priority_queue also does not appear to offer <. allowing="" an="" anyhow="" applies="" basically="" be="" but="" careful="" comparators="" correct="" course="" does="" don="" exact="" how="" i="" implies="" is="" it="" must="" not="" of="" operator="" out.="" overload="" oversight.="" passing="" properties="" provide="" reasoning="" rule="" said="" same="" see="" should="" so="" sort="" that:="" the="" therefore="" this="" to="" useful.="" user="" very="" what="" with="" you=""> – Nir Friedman May 15 '15 at 23:03
  • Anyhow, I think we passed the constructive part of the talk after the first two posts. You don't like this technique, ergo it's presence is an accident. I think it's ok (but not my first choice in most cases), and think the standard likes to keep options open for its users. Agree to disagree. – Nir Friedman May 15 '15 at 23:07
  • Whether or not I like the loophole is irrelevant to the quality of your answer. Your answer is based on the assumption that the loophole is intentional. And I don't think that's the case, based on the papers/books/talks by Stepanov which I've read. Of course, whether or not it's intentional can only be definitely answered by either the SGI STL creators or the C++ Standard Committee. -- On the topic of `unordered_*` and `priority_queue`: I'll try to find out why they don't support ` – dyp May 15 '15 at 23:26
  • It is indeed dangerous. Consider the case when you call into a function defined in `std`, which uses expressions like `a < b`. In this case, the lookup ends in `std` if the compiler finds a `operator – Lingxi May 16 '15 at 03:56
  • Luigi, this is only because I defined < in a different namespace than A. I did this for demonstration purposes. If you define them in the same namespace, which is the natural and standard thing to do, this behavior is impossible. I hardly see how this is dangerous. – Nir Friedman May 16 '15 at 04:10
  • @NirFriedman Users of the standard library are not supposed to define in `std` unless explicitly allowed by the standard. – Lingxi May 16 '15 at 05:52
  • Who said anything about defining in std?3) For arguments whose type is a class template specialization, in addition to the class rules, the following types are examined and their associated classes and namespaces are added to the set a) The types of all template arguments provided for type template parameters (skipping non-type template parameters and skipping template template parameters). So for a vector of user defined type, it's not an issue. – Nir Friedman May 16 '15 at 12:24