Использование пользовательских преобразований с неявными преобразованиями в сравнении

Я пытаюсь понять, почему следующий код не позволяет осуществлять неявное преобразование.

#include <string>
using namespace std;

struct HasConversionToString {
  HasConversionToString(const string& s_) : s{s_} {}
  string s;
  operator const string&() const { return s; }
};

int main() {
  string s{"a"};
  HasConversionToString obj{"b"};
  return s < obj;
}

Оба clang и gcc не могут найти действительный способ сравнить два объекта с ошибками по строкам:

clang++ -std=c++14 -Wall -Wextra -pedantic conversion.cpp -o test
conversion.cpp:13:12: error: invalid operands to binary expression ('string' (aka 'basic_string<char>') and 'HasConversionToString')
  return s < obj;
         ~ ^ ~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/stl_pair.h:220:5: note: candidate template ignored: could not match
      'pair' against 'basic_string'
    operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/stl_iterator.h:298:5: note: candidate template ignored: could not match
      'reverse_iterator' against 'basic_string'
    operator<(const reverse_iterator<_Iterator>& __x,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/stl_iterator.h:348:5: note: candidate template ignored: could not match
      'reverse_iterator' against 'basic_string'
    operator<(const reverse_iterator<_IteratorL>& __x,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/stl_iterator.h:849:5: note: candidate template ignored: could not match
      '__normal_iterator' against 'basic_string'
    operator<(const __normal_iterator<_IteratorL, _Container>& __lhs,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/stl_iterator.h:856:5: note: candidate template ignored: could not match
      '__normal_iterator' against 'basic_string'
    operator<(const __normal_iterator<_Iterator, _Container>& __lhs,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/stl_iterator.h:1089:5: note: candidate template ignored: could not match
      'move_iterator' against 'basic_string'
    operator<(const move_iterator<_IteratorL>& __x,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/stl_iterator.h:1095:5: note: candidate template ignored: could not match
      'move_iterator' against 'basic_string'
    operator<(const move_iterator<_Iterator>& __x,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/basic_string.h:4989:5: note: candidate template ignored: could not match
      'basic_string<type-parameter-0-0, type-parameter-0-1, type-parameter-0-2>' against 'HasConversionToString'
    operator<(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/basic_string.h:5001:5: note: candidate template ignored: could not match
      'const _CharT *' against 'HasConversionToString'
    operator<(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.0/../../../../include/c++/5.3.0/bits/basic_string.h:5013:5: note: candidate template ignored: could not match
      'const _CharT *' against 'string' (aka 'basic_string<char>')
    operator<(const _CharT* __lhs,
    ^
1 error generated.

тогда как следующий код работает отлично, когда я явно передал объект в строку.

#include <string>
using namespace std;

struct HasConversionToString {
  HasConversionToString(const string& s_) : s{s_} {}
  string s;
  operator const string&() const { return s; }
};

int main() {
  string s{"a"};
  HasConversionToString obj{"b"};
  return s < static_cast<string>(obj);
}

на основе правил и примеров, перечисленных в на cppreference для неявных бросков, я не вижу причин, по которым это не должно работать. Я предполагаю, что и clang, и gcc не испортили одно и то же, поэтому я предполагаю, что у меня есть концептуальное недоразумение.

Ответы

Ответ 1

Тот, который вы хотите вызвать, является шаблоном функции:

template<class charT, class Traits, class Alloc>
bool operator<(std::basic_string<charT, Traits, Alloc> const& lhs,
               std::basic_string<charT, Traits, Alloc> const& rhs);

Вычет не выполняется для второго аргумента, потому что a HasConversionToString не является аргументом std::basic_string - аргумент шаблона не просматривает неявные преобразования. В результате этот шаблон функции удаляется из разрешения перегрузки.

std::experimental::basic_string_view имеет аналогичную проблему, которая была решена с помощью правила "достаточных дополнительных перегрузок" (библиотека должна добавить достаточно перегрузок, чтобы сравнение между basic_string_view и чем-то конвертируемым на одно работает).

Вы действительно не хотите такой вещи для basic_string, хотя - иметь <, возможно, бесшумно вызывая поездку в кучу, на самом деле не очень хорошая идея.