Need help understanding why this invalid C++ code compiles successfully

Viewed 104

The following code compile correctly for me in some environments (e.g. on compiler explorer with GCC 9.3.0) and complains in others (CentOS 7.9.2009 (Core), GCC 9.3.1).

#include <iostream>
#include <string>
using namespace std;

int main() {
    std::string name="aakash";
    name.erase(name.begin()+2, name.cend());
    return 0;
}

When I get an error, the error is:

test.cpp:6:40: error: no matching function for call to 'std::basic_string<char>::erase(__gnu_cxx::__normal_iterator<char*, std::basic_string<char> >, std::basic_string<char>::const_iterator)'
    6 |  name.erase(name.begin()+2, name.cend());
      |                                        ^
In file included from /opt/rh/devtoolset-9/root/usr/include/c++/9/string:55,
                 from /opt/rh/devtoolset-9/root/usr/include/c++/9/bits/locale_classes.h:40,
                 from /opt/rh/devtoolset-9/root/usr/include/c++/9/bits/ios_base.h:41,
                 from /opt/rh/devtoolset-9/root/usr/include/c++/9/ios:42,
                 from /opt/rh/devtoolset-9/root/usr/include/c++/9/ostream:38,
                 from /opt/rh/devtoolset-9/root/usr/include/c++/9/iostream:39,
                 from test.cpp:1:
/opt/rh/devtoolset-9/root/usr/include/c++/9/bits/basic_string.h:4698:7: note: candidate: 'std::basic_string<_CharT, _Traits, _Alloc>& std::basic_string<_CharT, _Traits, _Alloc>::erase(std::basic_string<_CharT, _Traits, _Alloc>::size_type, std::basic_string<_CharT, _Traits, _Alloc>::size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int]'
 4698 |       erase(size_type __pos = 0, size_type __n = npos)
      |       ^~~~~
/opt/rh/devtoolset-9/root/usr/include/c++/9/bits/basic_string.h:4698:23: note:   no known conversion for argument 1 from '__gnu_cxx::__normal_iterator<char*, std::basic_string<char> >' to 'std::basic_string<char>::size_type' {aka 'long unsigned int'}
 4698 |       erase(size_type __pos = 0, size_type __n = npos)
      |             ~~~~~~~~~~^~~~~~~~~
/opt/rh/devtoolset-9/root/usr/include/c++/9/bits/basic_string.h:4714:7: note: candidate: 'std::basic_string<_CharT, _Traits, _Alloc>::iterator std::basic_string<_CharT, _Traits, _Alloc>::erase(std::basic_string<_CharT, _Traits, _Alloc>::iterator) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::basic_string<_CharT, _Traits, _Alloc>::iterator = __gnu_cxx::__normal_iterator<char*, std::basic_string<char> >; typename _Alloc::rebind<_CharT>::other::pointer = char*]'
 4714 |       erase(iterator __position)
      |       ^~~~~
/opt/rh/devtoolset-9/root/usr/include/c++/9/bits/basic_string.h:4714:7: note:   candidate expects 1 argument, 2 provided
/opt/rh/devtoolset-9/root/usr/include/c++/9/bits/basic_string.h:4734:7: note: candidate: 'std::basic_string<_CharT, _Traits, _Alloc>::iterator std::basic_string<_CharT, _Traits, _Alloc>::erase(std::basic_string<_CharT, _Traits, _Alloc>::iterator, std::basic_string<_CharT, _Traits, _Alloc>::iterator) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::basic_string<_CharT, _Traits, _Alloc>::iterator = __gnu_cxx::__normal_iterator<char*, std::basic_string<char> >; typename _Alloc::rebind<_CharT>::other::pointer = char*]'
 4734 |       erase(iterator __first, iterator __last);
      |       ^~~~~
/opt/rh/devtoolset-9/root/usr/include/c++/9/bits/basic_string.h:4734:40: note:   no known conversion for argument 2 from '__normal_iterator<const char*,[...]>' to '__normal_iterator<char*,[...]>'
 4734 |       erase(iterator __first, iterator __last);
      |                               ~~~~~~~~~^~~~~~
      |                               ~~~~~~~~~^~~~~~

The error looks reasonable to me because, as per C++ documentation, both arguments to std::basic_string::erase should either be of type iterator or const_iterator.

So, when it works (e.g. here), what allows it to work?

1 Answers

Since C++11, both arguments to the two-iterator overload of std::string::erase() are const_iterator. See form #3 on this cppreference page (the one you linked in your question).

Further, the C++11 Standard requires that a conatiner's iterator types are convertible to the equivalent const_iterator.

From this Draft C++11 Standard, in §23.2.1 [container.requirements.general], Table 96 has the following entry (bold emphasis mine):

Expression Return Type Operational Semantics Assertion/note
X::iterator iterator type
whose value
type is T
any iterator category that
meets the forward iterator
requirements. convertible to
X::const_iterator.

So, in your call to .erase(), the first argument is being converted to a string::const_iterator.

Thus, the bug is in those compilers that don't accept your call, assuming you have set them also to use the C++11 (or later) standard.

Related