11

I've seen the following type mistake a couple of times while working with C++ code:

QString str = str.toUpper();

This can be a fairly easy mistake to make and yet it compiles and executes (sometimes with crashes, sometimes without). I can't see any circumstances under which it would be something that you'd actually want to do.

Some testing has revealed that the copy constructor is invoked, not the default one, and that the object is being given itself from within the copy constructor.

Can anyone explain why this isn't a compiler error, or even a warning?

Chris
  • 17,119
  • 5
  • 57
  • 60
  • 1
    This is designated as undefined behavior (which doesn't require a diagnostic), presumably because there are one or more instances of this sort of code where it would be overly complicated to diagnose. – Mark B Oct 10 '11 at 20:35
  • Probably because the compiler doesn't know that toUpper() returns the same instance? I can imagine it's very difficult for compiler writers to check that. – Karel Petranek Oct 10 '11 at 20:36
  • @MarkB: this is defined behavior, see my answer. – Daniel Oct 10 '11 at 20:37

2 Answers2

7

Technically the object str is defined when you reach the equal sign, so it can be used at that point.

The error is in trying to initialize the object with itself, and the compiler is allowed to warn about that (if it is able to detect it). However, as the detection is not possible in every case, the compiler is not required.

For example int x = f(x); is entirely correct if int f(const int&) doesn't use the value of its parameter. How is the compiler to know that if it hasn't seen the function body yet?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • 1
    That makes sense. I think I would argue (for sanity's sake, if nothing else) that calling f(x) on an x that hasn't been constructed yet should still be an error, since as you mentioned the only scenario where it would be safe is the one in which the function doesn't use it. – Chris Oct 10 '11 at 21:12
  • Ah yes, but the function can *write* to it, which is actually ok for builtin types. – Mark B Oct 10 '11 at 21:13
  • It is worth noting that this syntax IS specifically forbidden when the object is defined via the `auto` keyword, but for type inference reasons. `auto x = x + 1;` is not valid and compliant compilers should reject it. – Michael Price Oct 10 '11 at 21:14
  • @Chris - It actually could save a pointer or a reference to the object and use that *later*. It just cannot use the value (as there isn't one yet). C and C++ are known for not forbidding things that are just only remotely useful. – Bo Persson Oct 10 '11 at 21:16
  • So what exactly is the state of `x` at the equality sign? Which constructor has been invoked at that point? – Kerrek SB Oct 10 '11 at 21:24
  • A common use for this is passing `this` to a base class constructor... it looks different, but the end result is the same. – Dennis Zickefoose Oct 10 '11 at 21:40
  • "detection is not possible in every case" That's a bit extreme. :) It's possible, just not required. – GManNickG Oct 10 '11 at 22:07
  • @GMan: what if the compiler doesn't see the definition of `f` in the current translation unit? (Possibly only "visible" at runtime if you consider shared libraries (which fall outside the standard, but still)) – Mat Oct 11 '11 at 07:19
  • @Mat: Then it can hold off until it does know. In principle, anything a human can do, statically, a compiler can do. It's only a matter of how much time and desire someone has to actually go through with it. – GManNickG Oct 11 '11 at 07:29
  • @GMan: the _C++ compiler_ can hold until runtime? i.e. inspect (compiled) code at runtime to decide whether it is safe? Sure from a theoretical point of view but you've got one hell of a runtime environment there. – Mat Oct 11 '11 at 07:37
  • @Mat: Yup. Compiler output is, as you know, completely implementation-defined. It's only by history that C++ is statically compiled, and not run in a VM, for example. – GManNickG Oct 11 '11 at 08:01
  • @GMan: (I fail to see how this potential runtime checking could enable the compiler to give a correct diagnostics at compile time though (warn 4324: your code will fail on 2015/11/03 because you'll be changing the implementation of `f`?)) – Mat Oct 11 '11 at 08:35
  • @Mat: Sorry, by throwing in VM I kind of blurred the line between run-time and compile-time. Without any dynamic loading, it's entirely possible. Without it, no, but you can get closer with a VM, which will at least warn you once and never check again. – GManNickG Oct 11 '11 at 09:12
-4

There is no error or warning because its equivalent to:

QString str;
str = str.toUpper();

Just like

QString str = "aaa";

is same as

QString str;
str = "aaa";

To do this in the same statement you need to use constructor, which won't compile:

QString str(str.toUpper());

just like:

QString str("aaa");

is not equivalent to

QString str;
str = "aaa";
Daniel
  • 30,896
  • 18
  • 85
  • 139
  • 4
    It's definitely not equivalent to `QString str; str = str.toUpper();` just like `QString str = "aaa";` doesn't call the assignment operator. – Mark B Oct 10 '11 at 20:38
  • Are `QString str = "aaa";` and `QString str; std = "aaa"` actually the same? I'm not a C++ expert, but to me it seems the first calls a constructor while the second calls a default constructor and then `operator=`. [Codepad](http://codepad.org/9ZISFNlu) seems to agree. –  Oct 10 '11 at 20:39
  • -1: `QString str = "aaa"` will not call the default constructor. It will call the constructor that takes a `const char*`, and the argument to that constructor will be `"aaa"`. That is, it is **not** the same as default constructing the `QString` and then copy-assigning to it. – Nicol Bolas Oct 10 '11 at 20:41
  • @NicolBolas: It will also call the copy constructor. That call may be omitted, however, the syntax QString str = "aaa" requires an accessible copy-constructor regardless of whether the actual call is omitted or not – Armen Tsirunyan Oct 10 '11 at 21:10