2
votes

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

5

5 Answers

4
votes

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.

1
votes

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
}
1
votes

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.

0
votes

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.

0
votes

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.