Why this warning from IBM XL C/C++ compiler?
Asked Answered
A

4

8

Here's a minimum code example that illustrates the problem:

#include <iostream>

class Thing
{
   // Non-copyable
   Thing(const Thing&);
   Thing& operator=(const Thing&);

   int n_;

public:
   Thing(int n) : n_(n) {}

   int getValue() const { return n_;}
};

void show(const Thing& t)
{
   std::cout << t.getValue() << std::endl;
}

int main()
{
   show(3);
}

This yields the same error:

int main()
{
    show( Thing(3) );
}

IBM XL C/C++ 8.0 compiler under AIX emits these warnings:

"testWarning.cpp", line 24.9: 1540-0306 (W) The "private" copy constructor "Thing(const Thing &)" cannot be accessed.
"testWarning.cpp", line 24.9: 1540-0308 (I) The semantics specify that a temporary object must be constructed.
"testWarning.cpp", line 24.9: 1540-0309 (I) The temporary is not constructed, but the copy constructor must be accessible.

I also tried g++ 4.1.2 with "-Wall" and "-pedantic" and got no diagnostic. Why is access to the copy constructor required here? How can I eliminate the warning, besides making the object copyable (which is outside my control) or making an explicit copy to pass (when the real-life object is expensive to copy)?

Angulate answered 23/10, 2009 at 20:15 Comment(3)
Are you actually using that copy constructor within the implementation of the class somewhere?Astatic
No. I've posted the entire code file that produced the diagnostics.Angulate
FYI: I also tried with VC++ 2005 & 2008, compiles with no warnings. So it seems you are right, the problem seems to be rather IBM compiler specificNoctilucent
S
9

The rules for this are in §8.5.3/5 of the standard. There are three basic situations identified. The first involve the initializer ('3' in your case) being either an lvalue, or having class type. Since neither of those is true, what you have is the third case: initializing a const reference with an rvalue that does not have a class type. This case is covered by the final bullet in 8.5.3/5:

Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 must be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed.

Edit: rereading, I think IBM has it right. I was previously thinking of the possibility of having to copy the temporary, but that's not the source of the problem. To create the temporary using non-reference copy initialization as specified in §8.5, it needs the copy ctor. In particular, at this point it's equivalent to an expression like:

T x = a;

This is basically equivalent to:

T x = T(a);

I.e. it's required to create a temporary, then copy the temporary to the object being initialized (which, in this case, is also a temporary). To summarize the required process, it's roughly equivalent to code like:

T temp1(3);
T temp2(temp1); // requires copy ctor
show(temp2);    // show's reference parameter binds directly to temp2
Stingo answered 23/10, 2009 at 20:50 Comment(7)
Good info. Thanks for the research, Jerry.Angulate
That's really weird, but I agree with your/IBM's reading of the standard.Marmoset
<quote>it's required to create a temporary</quote>Technically: Its required to be able to create a temporary. Technically the compiler can optimize this out.Communicate
@Martin: well, yes, all requirements include an implicit: "or something equivalent, as long as they really are equivalent to the point that you can't tell the difference with conforming code."Stingo
What you say here makes sense. The only issue I have is that recent versions of g++ and comeau both reject "Thing t = 3" but they accept "show(3)". That is not to say that what you say is wrong, but especially that Comeau doesn't match the behaviour makes me wonder....Doggerel
I can't guarantee it by any means, but my guess is that Comeau is already implementing something closer to the rules in the current C++ 0x draft. The addition of rvalue references (in particular) means that the rules for initialization (especially of references) have been substantially rewritten.Stingo
Just looked at the June '09 draft, and this section hasn't changed too much (except for the addition of 391). This would be an interesting question on comp.std.c++ or if you have access the core reflector.Doggerel
M
3

C++ permits sufficiently-smart compilers to avoid copying temporary objects, the one violation of the as-if rule allowed by the standard. I'm not familiar with IBM's AIX C++ compiler, but it sounds like it thinks the show(3) call requires a temporary Thing to be copied. In that case, C++ requires that you have an accessible copy constructor even though your compiler is smart enough to avoid using it.

But why does show(3) require a copy in the first place? That I can't figure out. With luck, litb will be along in a bit.

Marmoset answered 23/10, 2009 at 20:26 Comment(3)
That's pretty much what I was thinking.Angulate
"But why does show(3) require a copy in the first place?" I don't think it should. The code should create a temporary object and bind that straight to the const Thing& function parameter. So I think Jerry is right: this is a bug.Brecciate
I've changed my mind -- after rereading the standard, I'm pretty sure it's not a bug.Stingo
D
1

My gut feeling is that Jerry's answer is correct, but there are a few questions still.

What is interesting is that there is a core issue covering the previous paragraph of that section (391). That issue relates to when the argument is the same class type. Specifically:

int main () {
  show ( Thing (3) );       // not allowed under current wording
                            // but allowed with Core Issue 391

  show ( 3 );               // Still illegal with 391
}

The change in Core Issue 391 only affects where the rvalue temporary has the same class type. The previous wording had:

If the initializer expression is an rvalue, with T2 a class type, and cv1 T1 is reference-compatible with cv2 T2, the reference is bound as follows:

[...]

The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.

That last line is what would make show(Thing(3)) illegal as per the current standard. The proposed wording for this section is:

If the initializer expression is an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2", the reference is bound to the object represented by the rvalue (see 3.10 [basic.lval]) or to a sub-object within that object.

At this point, I considered that g++ may have updated its behaviour as per 391 but that the change accidentally included the copy-initialization case. However, that is not demonstrated by the versions of g++ that I tested with:

class A{
public:
  A ();
  A (int);
private:
  A (A const &);
};

void foo (A const &);

void foo ()
{
  A a = 3 ;     // 3.2.3 (ERROR), 3.4.6(ERROR), 4.4.0(ERROR), Comeau(ERROR)
  
  foo ( 3 ) ;   // 3.2.3 (OK), 3.4.6(OK), 4.4.0(OK), Comeau(OK)
  foo ( A() );  // 3.2.3 (OK), 3.4.6(ERROR), 4.4.0(OK), Comeau(OK)
  foo ( A(3) ); // 3.2.3 (OK), 3.4.6(ERROR), 4.4.0(OK), Comeau(OK)
}

I cannot find fault in Jerry's interpretation for the foo (3) case, however, I do have doubts due to the discrepency between the different compiler behaviours.

Doggerel answered 26/10, 2009 at 13:15 Comment(0)
S
0

What happens if you try naming the temporary Thing?

Thing temp(3);
show(temp);

Slander answered 23/10, 2009 at 20:41 Comment(1)
That eliminates the messages. We'll probably do essentially this, but probably with a heap allocation. It's not as fast, but the real-life object is pretty large to be putting on the stack. So maybe it's better not to rely on the temporary anyway.Angulate

© 2022 - 2024 — McMap. All rights reserved.