Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it not a compiler error to assign to the result of a substr call?

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

like image 536
blahedo Avatar asked Dec 12 '16 03:12

blahedo


3 Answers

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:

  1. Return a const object.
  2. Use lvalue ref-qualifier on assignment operator.

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.

like image 188
M.M Avatar answered Oct 18 '22 18:10

M.M


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.

like image 43
danieltm64 Avatar answered Oct 18 '22 18:10

danieltm64


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.

like image 7
Kirill Kobelev Avatar answered Oct 18 '22 18:10

Kirill Kobelev