18

The issue is clear with the following code:

#include <functional>
#include <iostream>
#include <vector>

int main() {
  //std::vector<int> a, b;
  int a = 0, b = 0;
  auto refa = std::ref(a);
  auto refb = std::ref(b);
  std::cout << (refa < refb) << '\n';
  return 0;
}

If I use the commented std::vector<int> a, b; instead of int a = 0, b = 0;, then the code does not compile on any of GCC 5.1, clang 3.6, or MSVC'13. In my opinion, std::reference_wrapper<std::vector<int>> is implicitly convertible to std::vector<int>& which is LessThanComparable, and thus it should be LessThanComparable itself. Could someone explain this to me?

Lingxi
  • 14,579
  • 2
  • 37
  • 93

2 Answers2

22

The issue is that the non-member operator< for std::vector is a function template:

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

Implicit conversions are not considered when doing template type deduction here, [temp.arg.explicit] emphasis on if:

Implicit conversions (Clause 4) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.

But in this case, the parameter type does participate in deduction. That's why it can't be found. Had we written our own non-template operator<:

bool operator<(const std::vector<int>& lhs, const std::vector<int>& rhs)
{
    return true;
}

Your code would work as expected. To use the generic one though, you will have to explicitly pull out the reference:

std::cout << (refa.get() < refb.get()) << '\n';
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Very interesting. What is the rationale for the part of the spec that you quoted? – George Hilliard May 15 '15 at 15:26
  • @thirtythreeforty: http://stackoverflow.com/questions/6677072/overload-resolution-failure-when-streaming-object-via-implicit-conversion-to-str/7505108#comment9064889_6677134 – Lightness Races in Orbit May 15 '15 at 15:35
  • If `operator<` was written as a `friend bool` Koenig operator, would it be found by conversion? I'm wondering if this is a matter of "vector is old, they didn't know the right way" or "pseudo-references are hard, reference wrapper should add SFINAE detecting overloads" (probably both). – Yakk - Adam Nevraumont May 15 '15 at 15:43
  • @Yakk As a non-template (e.g. `friend bool operator<(const vector& lhs, const vector& rhs) { ... }`), yes it would be found. – Barry May 15 '15 at 16:14
  • @Barry Shouldn't `friend bool operator<(const vector& lhs, const vector& rhs) { ... }` still be a template? – Lingxi May 15 '15 at 16:35
  • @lingxi no. It is a non-template function that can only be found via ADL. In fewer words, Koenig operators are magic. – Yakk - Adam Nevraumont May 15 '15 at 16:49
  • @Barry I believe it. I was just concerned that the conversion operators wouldn't cause ADL to kick off in the context of the result type (I could not remember if they first find overloads, then do conversion or look at all conversions, and overloads for each conversion). Gah, template conversion operators doing Koenig lookup? I can't see how it would be possible, but not certain where it falls apart. Anyhow, this is a tangent. – Yakk - Adam Nevraumont May 15 '15 at 16:52
  • @Yakk Thought I'd [ask](http://stackoverflow.com/q/30265207/2069064) the obvious followup. – Barry May 15 '15 at 17:15
0

Are you certain that

std::vector<int> a, b;

Is doing what it is supposed to? Take this for example

#include <functional>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> a, b;
  //int a = 0, b = 0;
  a.push_back(42);
  a.push_back(6);
  a.push_back(15);
  for (int ii=0; ii<43; ii++) {
    b.push_back(ii);
  }
  auto refa = std::ref(a);
  auto refb = std::ref(b);
  std::cout<<&refa<<std::endl;
  std::cout<<&refb<<std::endl;
  std::cout<<"Contents of vector A"<<std::endl;
  for(auto n : a)
  {
    std::cout<<' '<<n;
  }
  std::cout<<std::endl<<"Contents of vector b: ";
  for (auto n : b){
    std::cout<<' '<<n;
  }
  //std::cout << (refa < refb) << '\n';
  return 0;
}

Which results in

0x7fff5fbff0c0
0x7fff5fbff0b8
Contents of vector A
 42 6 15
Contents of vector b:  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

Ultimately

std::vector<int> a, b;

Creates two separate vectors of integers called a and b, both of which have no contents; this is not how one would declare a single vector with members a and b.

int a=0, b=0;

Declares two separate integers called a and b, which each have a value of 0. Those two code snippets declare completely different variables and should not be used interchangeably.

Matt
  • 545
  • 3
  • 16