using user-defined conversions with implicit conve

2019-04-04 07:53发布

问题:

I am struggling to understand why the following code does not allow an implicit conversion to occur.

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

Both clang and gcc fail to find a valid way to compare the two objects with errors along the lines of:

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.

whereas the following code works fine, when I explicitly cast the object to a string.

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

based on the rules and examples listed on cppreference for implicit casts, I see no reason this shouldn't work. I assume that both clang and gcc didn't screw up the same thing, so I imagine that I've got a conceptual misunderstanding.

回答1:

The one you want to call is a function template:

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);

Deduction fails for the second argument because a HasConversionToString is not a std::basic_string - template argument deduction doesn't look through implicit conversions. As a result, that function template is removed from overload resolution.

std::experimental::basic_string_view has a similar problem, which was solved by a "sufficient additional overloads" rule (the library must add enough overloads so that comparison between a basic_string_view and something convertible to one works).

You don't really want such a thing for basic_string, though - having < possibly silently causing a trip to the heap is not really a good idea.



回答2:

http://en.cppreference.com/w/cpp/language/template_argument_deduction#Implicit_conversions

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.