I just discovered the most baffling error and I don't understand why the compiler did not flag it for me. If I write the following:
string s = "abcdefghijkl";
cout << s << endl;
s.substr(2,3) = "foo";
s.substr(8,1) = '.';
s.substr(9,1) = 4;
cout << s << endl;
The compiler has no problem whatsoever with this, and the assignment statements appear to have no effect, based on what's printed out. In contrast,
s.front() = 'x';
has the effect I'd expect (since front
returns a reference to a character) of changing the underlying string, and
s.length() = 4;
also has the expected effect of generating a compiler error complaining that you can't assign to something that isn't an lvalue, because length
returns an integer. (Well, a size_t
anyway.)
So... why on earth does the compiler not complain about assigning to the result of a substr
call? It returns a string value, not a reference, so it shouldn't be assignable, right? But I've tried this in g++
(6.2.1) and clang++
(3.9.0), so it doesn't seem to be a bug, and it also doesn't seem to be sensitive to C++ version (tried 03, 11, 14).
The result of substr()
is a std::string
temporary object -- it's a self-contained copy of the substring, not a view on the original string.
Being a std::string
object, it has an assignment operator function, and your code invokes that function to modify the temporary object.
This is a bit surprising -- modifying a temporary object and discarding the result usually indicates a logic error, so in general there are two ways that people try to improve the situation:
const
object.Option 1 would cause a compilation error for your code, but it also restricts some valid use-cases (e.g. move
-ing out of the return value -- you can't move out of a const
string).
Option 2 prevents the assignment operator being used unless the left-hand side is an lvalue. This is a good idea IMHO although not all agree; see this thread for discussion.
In any case; when ref-qualifiers were added in C++11 it was proposed to go back and change the specification of all the containers from C++03, but this proposal was not accepted (presumably, in case it broke existing code).
std::string
was designed in the 1990s and made some design choices that seem poor today in hindsight, but we're stuck with it. You'll have to just understand the problem for std::string
itself, and perhaps avoid it in your own classes by using ref-qualifiers, or views or whatever.
The reason your code compiles is because it is legal C++. Here's a link that explains what's going on.
https://accu.org/index.php/journals/227
It's pretty long, so I'll quote the most relevant part:
Non-class rvalues are not modifiable, nor can have cv-qualified types (the cv-qualifications are ignored). On the contrary, the class rvalues are modifiable and can be used to modify an object via its member functions. They can also have cv-qualified types.
So the reason you can't assign to the rvalue returned by std::string::length
is because it isn't an instance of a class, and the reason you can assign to the rvalue returned from std::string::substr
is because it is an instance of a class.
I don't quite get why the language is defined this way, but that's how it is.
Look at the code:
s.substr(2,3) = "foo";
The function call to substr
returns a string, that is an object and a temporary value. After that you modify this object (actually by calling the overloaded assignment operator from the std::string
class). This temporary object is not saved in any way. Compiler simply destroys this modified temporary.
This code does not make sense. You may ask, why compiler is not giving a warning? The answer is that compiler might be better. Compilers are written by people, not gods. Unfortunately C++ allows tons of various ways of writing senseless code or code that triggers undefined behavior. This is one of the aspects of this language. It requires better knowledge and higher attention from the programmer compared to many other languages.
I just checked with MSVC 2015, the code:
std::string s1 = "abcdef";
s1.substr(1, 2) = "5678";
compiles fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With