2

Suppose I have something like the following in test.cxx (and that I do the object slicing at 1 intentionally):

class A {
};

class B : public A {
  // prevent copy construction and assignment
  B(const B& other);
  B& operator=(const B& other);
public:
  explicit B(){}
};

class C {
  A m_a;
public:
  explicit C() : m_a( B() ) {} // 1
};

I expect this to work, as in 1 the copy-constructor of class A (here it is compiler-generated and public) should be called. This code also compiles fine on recent compilers (I tried g++-4.4 and Intel 11.0), however older compilers (such as g++-4.2 and g++-4.0) try to invoke the copy-constructor of B, which I declared to be private, resulting in:

test.cxx: In constructor ‘C::C()’:
test.cxx:7: error: ‘B::B(const B&)’ is private
test.cxx:16: error: within this context

Now, in my build-system I want to check whether the compiler supports above code. Question is, however, is this standard-conforming code? And what would be the proper name for such a test?

Edit: I'm sorry, Intel compiler version 10.1 and 11.0 both issue the following: warning #734: "B::B(const B &)" (declared at line 6), required for copy that was eliminated, is inaccessible

Michael Wild
  • 24,977
  • 3
  • 43
  • 43

5 Answers5

4

I dare to disagree with Comeau in this case. In fact, the following code fails to compile as expected, because binding an rvalue to a const reference requires accessible copy constructor.

class A {
};

class B : public A {
  B(const B& other);
  B& operator=(const B& other);
public:
  explicit B(){}
};

int main()
{
    A const & a = B();
}

Per 8.5.3/2, "[...] Argument passing (5.2.2) and function value return (6.6.3) are initializations," and as such the code should be ill-formed.

Edit: I still firmly believe that the code is ill-formed according to C++03. However, I've just read the relevant section of the working draft for C++0x and it seems that it no longer requires the copy constructor to be available. Perhaps that's the reason your code started compiling when you moved from gcc-4.2 to gcc-4.3.

Edit: To clarify, the reason why B::B(const B &) must be accessible is due to the binding of B() to the first parameter of A::A(const A &) (which is, of course, called when m_a is being initialized).

Edit: Regarding the difference between C++03 and C++0x, litb was kind enough to find the relevant defect report.

avakar
  • 32,009
  • 9
  • 68
  • 103
  • But wouldn't that mean that also something like the following is invalid? class A { A(const A&); A& operator=(const A&); }; void f(const A& a) {...} int main() { A a; f( a ); } – Michael Wild Aug 20 '09 at 08:47
  • But why then does "int main(){B b; A const& a = b;}" work? Is it something to do with temporary objects? – Michael Wild Aug 20 '09 at 09:11
  • In the original code, there is an accessible copy constructor - the one belonging to class A. –  Aug 20 '09 at 09:25
  • 1
    Michael, in `int main(){B b; A const& a = b;}`, the expression `b` is an lvalue, whereas `B()` would be an rvalue. – avakar Aug 20 '09 at 10:24
  • Neil, but you're initializing a const reference with an rvalue expression of type `B`, where `B` is a class type, and as such copy constructor of `B` must be available. – avakar Aug 20 '09 at 10:27
  • I disagree - in the original code, you are initialising a object of type A, which means that A's (not B's) copy constructor will be used. and this is available. –  Aug 20 '09 at 10:29
  • Neil, no, standard clearly says: "A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows [...] A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary". Note, that the "sic" is not mine, it's part of the text of the standard. – avakar Aug 20 '09 at 10:36
  • Yes, it says "a constructor is called" - it doesn't say which one, so doesn't back your position up. –  Aug 20 '09 at 10:39
  • How can a temporary of type "cv1 T2" be created using T1's copy constructor? – avakar Aug 20 '09 at 10:41
  • Well, through implicit conversion, because T2 inherits from T1. – Michael Wild Aug 20 '09 at 11:10
  • Michal, in C++, constructors are not inherited. – avakar Aug 20 '09 at 11:19
  • No need to, I call it explicitly in "m_a(B())". All the compiler needs to do is to convert a (temporary) object of type B to type A, which definitely is standard. – Michael Wild Aug 20 '09 at 11:25
  • 1
    Michal, maybe I wasn't clear. The problem is, that before `A::A(const A&)` is called, the `B()` temporary (i.e. an rvalue) must be bound a const reference (the parameter of `A`'s copy constructor). It is that binding that requires `B`'s constructor to be accessible, even if no actual copying takes place. – avakar Aug 20 '09 at 11:30
  • Avakar, from what I dimly remember reading in one of my numerous C++ books. Confusingly, newer compilers seem to accept it, where older ones don't. I now also checked with Intel 10.1, and it issues warning 734 (http://software.intel.com/en-us/articles/cdiag734), telling me that ""B::B(const B &)" (declared at line 6), required for copy that was eliminated, is inaccessible". So, do newer compilers accept this out of courtesy, or is it in the new standard and they silently implemented it? – Michael Wild Aug 20 '09 at 11:51
  • 1
    The current standard is c++03 and it definitely requires accessible copy constructor on rvalue binds. As I said in my answer, the draft of the upcoming standard lifted that requirement. g++ and other popular compilers are in general already trying to implement changes that seem unlikely to be modified in the final standard. – avakar Aug 20 '09 at 11:58
  • Just for completeness; section 12.2/1 says it clearly: Even when the creation of the temporary object is avoided (class.copy), all the semantic restrictions must be respected as if the temporary object was created. [Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (clause class.access), shall be satisfied. ] – Michael Wild Aug 20 '09 at 12:14
  • 1
    No, that code does not compile on Comeau: "ComeauTest.c", line 13: error: "B::B(const B &)" is inaccessible (Even though the copy was eliminated, the standard still requires it to be accessible) – Fernando N. Aug 20 '09 at 13:48
  • I could have been more clear, I was referring to OP's code, not mine. Anyway, I've removed the line. – avakar Aug 20 '09 at 14:08
  • You may wanna mention http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#391 , tho. – Johannes Schaub - litb Aug 20 '09 at 14:38
  • 1
    What is the difference with `A const & a = static_cast( B() );` ? Last variant compiles OK. – Kirill V. Lyadvinsky Aug 20 '09 at 15:01
  • I don't think it should compile OK, just as `A b(B());` shouldn't. – avakar Aug 20 '09 at 15:09
  • Don't be confused of `A b(B());`, if it works. It doesn't do what you want. – Johannes Schaub - litb Aug 20 '09 at 17:52
  • 1
    @litb, about fnieto comment: if you disable c++0x extensions and force c++03 compliance then the code will not compile with comeau with the error reported by fnieto. – David Rodríguez - dribeas Aug 20 '09 at 20:31
  • What exactly is the problem with 'A a( B() )' (assuming c++0x)? How would it differ from what you expect? (I would expect slicing, if that is the point) – David Rodríguez - dribeas Aug 20 '09 at 20:58
  • dribeas, in C++0x there is no problem, `A a(B())` is well-formed and causes the object `a` to be created using `A`'s copy constructor with `B()` bound to its only (reference) parameter. The problem is in C++03, where the binding additionally requires `B` to have an accessible copy constructor. – avakar Aug 21 '09 at 06:16
  • @litb We were testing diferent code. You are right, the code form the answer compile, but not the code form the @Michael Wild answer. – Fernando N. Aug 21 '09 at 08:16
  • @dribeas, no the code in the question compiles with the comeau online compiler, without extensions and with strict mode. – Johannes Schaub - litb Aug 21 '09 at 15:13
  • @avakar, there is no object created by `A a(B())`. I was just noting that, and pointing that you need to do `A a((B()));` – Johannes Schaub - litb Aug 21 '09 at 15:16
  • @avak, The text in the Standard says that it is valid iff `A const& b((B()));` is valid. You of course need to include the reference. otherwise, you say "`static_cast(1)` is valid because `int a(1);` is valid". – Johannes Schaub - litb Aug 21 '09 at 15:20
  • litb, as always, you're right on both accounts :) Regarding `A a((B()));`: the most vexing parse problem is something I do not automatically spot and I'm bound to do the same mistake over and over again in the future. Re `A const& b((B()));` I dropped the reference by mistake and carried it all over my comments. Sorry, my bad. – avakar Aug 21 '09 at 16:34
  • Don't worry. I was kidding somewhat when i made these remarks, keeping the reason why it's not rejected "hidden" :) – Johannes Schaub - litb Aug 21 '09 at 21:13
1

Seems like older g++ compilers can't pass temporary objects by reference if there is no copy-constructor available:

class A { 
  A(const A& other);
  A& operator=(const A& other);
public:
  explicit A(){}
};
void f( const A& a ) {}
int main() {
  A a;
  f( a );    // fine
  f( A() );  // fails
}
Michael Wild
  • 24,977
  • 3
  • 43
  • 43
  • But that is not the situation in your original code. A copy constructor for A (the default one) is available. –  Aug 20 '09 at 09:27
1

I think it is standard-conforming code in C++0x, but not in C++03.

I'd name the test something like "copy construction from rvalue".

This was reported as an error, but gcc people argue here that is the correct behavior and give references to the standard.

[dcl.init.ref]/5, bullet 2, sub-bullet 1

If the initializer expression in an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2" the reference is bound in one of the following ways (the choice is implementation defined):
- The reference is bound to the object represented by the rvalue (see 3.10) or the sub-object within that object.
- A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.

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

C++0x standard removes the ambiguity and the reference is always bounded to the object represented by the rvalue, not needing the constructor to be accessible.

Fernando N.
  • 6,369
  • 4
  • 27
  • 30
0

I can't point you at a specific section of the standard, but it certainly looks OK to me, and compiles with Comeau C++ as well as the compilers you mention. As for a name for such a test, I guess "compiler compatibility" is as good as anything.

  • Thanks for the confirmation. However, I'd like a more specific name than just "compiler compatibility". If you saw something like "object slicing support" without above context, would that be understandable enough for you? – Michael Wild Aug 20 '09 at 08:30
0

It is valid. Calling B() constructs a B object; B's default compiler generated public constructor is called.

When a C object is constructed an A object will be constructed. As it is a value then static typing will be considered and slicing will occur on any object that is derived from A and that is used to copy construct an A object. Class A has a public copy constructor provided by the compiler. The compiler sees that a B object is also of type A. The compiler is only concerned with copy constructing an A object and it knows it can do this so it copies the A object within the B object using the A copy constructor to the C::m_a object. ie slicing due to static typing.

It is instructive to look at the memory of these objects. Put a char * data member in A initialised to "I'm an A" and a char * data member in B initialised to "I'm a B" and step through your debugger to monitor your objects. You should see those strings easy enough.

Sam
  • 694
  • 4
  • 6
  • I do agree, it is sensible to expect it to work as you say. Question is, does the standard agree? I know that there are some rather tricky issues related to temporaries... – Michael Wild Aug 20 '09 at 11:29
  • `B` doesn't have a compiler-generated default constructor and wouldn't have it even if the default constructor weren't declared explicitly. – avakar Aug 20 '09 at 11:33
  • @avakar - oops! for overlooking the declared B::B(). And your quite right again; B has a user declared copy constructor, it is a constructor, and so no compiler generated default constructor. Thanks. @Michael Wild - you know I've often tried to verify things like this in the C++ Standard but failed to pin them down. This is one of those! Slicing is not in the index. – Sam Aug 20 '09 at 13:10