18

Test code:

struct X {
    X (int x) {}
};

int main() {
    X x1(0);     // nothing!
    X x2 = 0;    // warning: unused variable
    X x3 = X(0); // warning: unused variable
}

Question

Why no warning is generated for x1?

Notes:

  • I'm compiling with the -Wall option.

  • Both GCC and Clang produce equivalent output.

  • Those 3 lines generate the same assembler instructions (placed some asm("nop") for clarity):

    nop                      
    lea    -0x8(%rbp),%rdi   
    mov    $0x0,%esi         
    callq  400600 <X::X(int)>
    nop                      
    lea    -0x10(%rbp),%rdi  
    mov    $0x0,%esi         
    callq  400600 <X::X(int)>
    nop                      
    lea    -0x18(%rbp),%rdi  
    mov    $0x0,%esi         
    callq  400600 <X::X(int)>
    nop
    
  • It does not happen with primitive types (e.g., typedef int X).

  • If the reason concerns the fact that there may be side effects in the constructor then the question becomes: why do I get the other two warnings?


Edit:

  • This is not a duplicate of this question IMO since in this case the constructor is non-trivial.
Community
  • 1
  • 1
cYrus
  • 2,976
  • 6
  • 29
  • 47
  • FWIW : VS2015 with all warnings only complains about the referenced `x` in `X (int x) {}`. – François Andrieux Feb 07 '17 at 20:39
  • Compiling this with g++ the error message says the flag for the warning is `-Wunused-but-set-variable`. Looking that up from [here](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) I get: *Warn whenever a local variable is assigned to, but otherwise unused (aside from its declaration). This warning is enabled by -Wall.* This leads me to believe that it is the presence of the `=` in the declaration that is surfacing the warning while direct construction does not surface the warning and most likely it is a bug – NathanOliver Feb 07 '17 at 21:15
  • Possible duplicate of [Why the compiler does not issue a warning when an object std::vector is declared but never used?](http://stackoverflow.com/questions/37918140/why-the-compiler-does-not-issue-a-warning-when-an-object-stdvector-is-declared) – ewindes Feb 07 '17 at 21:21
  • @ewindes see my edit. – cYrus Feb 07 '17 at 21:32
  • @NathanOliver the same bug in both GCC and Clang seems unlikely. – cYrus Feb 07 '17 at 21:38
  • I think a better question would be why the warning is generated for x2,x3. Since the constructor is non-trivial. – rustyx Feb 07 '17 at 21:58
  • @RustyX For `x3` it is generated, since the definition of `x3` is unnecessary for the function call. `X(0)` would do the same thing. In the first case you cannot omit the definition without taking away the function call. – Amin Negm-Awad Feb 22 '17 at 20:06

2 Answers2

9

The first one calls your user-defined constructor to perform direct initialization. This could have side-effects. Hence x1 is used. (Or at least it cannot easily enough be determined to be unused.)

The second one copy constructs. It calls your user-defined constructor to create a temporary. It then calls the default copy constructor to construct x2. (See below in edit for more detailed discussion and refinement.) Hence x2 can be determined to be unused because it was created by a compiler-generated constructor with no side-effects, and x2 itself does not appear anywhere else.

The third is like the second. A temporary is created via your user-provided constructor, and then copied. The temporary is used, but x3 itself is not.

See the accepted answer to this question: Is there a difference in C++ between copy initialization and direct initialization?

EDIT

Based on the comments, here's some further discussion.

First, there was the comment that the warning is misleading. This is perhaps somewhat subjective, but I would point out that warnings of this sort are often provided on a "best effort" basis only. It could be that something that is not guaranteed in general is true in a specific case if the compiler would only dig far enough into the code to check. At some point, though, the compiler developers have to draw a line. That means that you generally cannot count on a warning like this to catch every case of an unused variable. (On the other hand, if you have a variable that is used and you get a warning, that would be a bug.) My personal feeling is that the behavior demonstrated here is not misleading, but, again, I see that there's some personal interpretation in making such a call.

Second, it was pointed out by @FrançoisAndrieux that if you modified the example given by the OP to include a user-defined copy constructor, you still get the same warnings. That calls into question my explanation above, which referenced the default copy constructor. This touches on a second point of theory, which I'll follow up with a specific answer for this case. The point of theory is that, unless you really want to dig into the compiler itself and get its specific rules, the issue at hand is whether the compiler can reasonably know that the variable is unused, keeping in mind that there may be more than one way that the compiler could have reached its conclusion.

As the example was originally posted, I think my answer gives a way that the complier could have come to its conclusion. Another way, that seems to be applicable both to the original form and to the modified form suggested by the comment is based on copy elision. There's an extensive discussion of this here: What are copy elision and return value optimization? The key point for the current discussion is that, specifically for copy constructors, the compiler is allowed to ignore side effects. In fact, in some cases, the compiler is required to ignore the copy. Evidently, the compiler here is taking this into account, either directly or indirectly, when issuing the warning. This also probably goes to the point made by the OP that the assembly code in all three cases is the same - that's because the copy action has been optimized out.

Let me try now to anticipate the next question: "If the copy construct is elided, why aren't the three cases really the same?" The answer here, I think, is in which specific variable are unused. It's the unnamed, temp variables in the second and third case that are constructed, not the named variables x2 and x3. The temp variables fall into same pattern as the first case in the example and are "used" (or at least cannot be determined easily enough to be unused). That still leaves x2 and x3 unused by any standard once the copy construct is optimized out.

Community
  • 1
  • 1
Brick
  • 3,998
  • 8
  • 27
  • 47
  • While this is most likely the reason (and great answer you linked BTW) I still find that warnings somewhat misleading... – cYrus Feb 07 '17 at 23:25
  • Why does the warning persist if you provide a user defined copy constructor? – François Andrieux Feb 08 '17 at 14:38
  • @FrançoisAndrieux Due to copy elision, I think the compiler is allowed to ignore any side effects of the copy constructor in this case. You raise a good point though - At minimum I will probably need to edit the answer in a bit. – Brick Feb 08 '17 at 15:34
  • @FrançoisAndrieux Extended the discussion to try to cover what I think is happening in the case that you described. – Brick Feb 08 '17 at 16:41
3

@Brick answer's follows. For different semantics you can end up with same final object code, to prove that try add -fno-elide-constructors to your flags, elision is the culprit that leads to same object code, even in -O0.

oblitum
  • 11,380
  • 6
  • 54
  • 120