Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected const reference behavior

Tags:

c++

gcc4

#include <iostream>

class A { 
  public:  
    A(){ cerr << "A Constructor" << endl; }  
    ~A(){ cerr << "A Destructor" << endl; }  
    A(const A &o){ cerr << "A Copy" << endl; } 
    A& operator=(const A &o){ cerr << "A Assignment" << endl; return *this; }
};


class B : public A { 
  public:  
    B() : A() { cerr << "B Constructor" << endl; }  
    ~B(){ cerr << "B Destructor" << endl; }
  private:
    B(const B &o) : A() { cerr << "B Copy" << endl; } 
    B& operator=(const B &o){ cerr << "B Assignment" << endl; return *this; }
};

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

In GCC 4.2, I get this message:

In function 'int main()':
Line 16: error: 'B::B(const B&)' is private
compilation terminated due to -Wfatal-errors.

If I remove the "private" from B, I get the output I expect:

A Constructor
A Constructor
B Constructor
B Destructor
A Destructor
A Destructor

My question is: why does making a method which isn't called private change whether this code compiles? Is this standard-mandated? Is there a workaround?

like image 641
Zachary Vance Avatar asked Jul 14 '10 18:07

Zachary Vance


People also ask

What does const reference mean?

If you are using const reference, you pass it by reference and the original data is not copied. In both cases, the original data cannot be modified from inside the function.

Can you modify a const reference C++?

But const (int&) is a reference int& that is const , meaning that the reference itself cannot be modified.

Should I always use const reference?

Yes, you should use const whenever possible. It makes a contract that your code will not change something. Remember, a non-const variable can be passed in to a function that accepts a const parameter. You can always add const, but not take it away (not without a const cast which is a really bad idea).

When should you use a const reference parameter?

Pass Using Const Reference in C++ Now, we can use the const reference when we do not want any memory waste and do not change the variable's value. The above code will throw a compile error as num = num +10 is passed as a const reference.


3 Answers

The important verbiage in the current standard (C++03) seems to be in §8.5.3, which explains how references are initialized (In these quotes, T1 is the type of the reference being initialized and T2 is the type of the initializer expression).

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 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 to a 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.

So, even if the implementation binds the reference directly to the temporary object, the copy constructor must be accessible.

Note that this is changed in C++0x, per the resolution of CWG defect 391. The new language reads (N3092 §8.5.3):

Otherwise, if T2 is a class type and

-- the initializer expression is an rvalue and "cv1 T1" is reference-compatible with "cv2 T2,"

-- T1 is not reference-related to T2 and the initializer expression can be implicitly converted to an rvalue of type "cv3 T3" (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through overload resolution (13.3)),

then the reference is bound to the initializer expression rvalue in the first case and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).

The first case applies and the reference is "bound directly" to the initializer expression.

like image 126
James McNellis Avatar answered Nov 06 '22 17:11

James McNellis


So what you're using is 'copy-initialization':

8.5/11 Initializers

The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. ...

The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form

T x = a;

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form

T x(a);

In 13.3.1.3 "Initialization by constructor", the overloads for the constructor chosen are:

When objects of class type are direct-initialized (8.5), or copy-initialized from an expression of the same or a derived class type (8.5), overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy initialization, the candidate functions are all the converting constructors (12.3.1) of that class.

So, for copy-initialization, the copy constructor must be available. However, the compiler is permitted to 'optimize away' the copy:

12.2/1 Temporary objects

Even when the creation of the temporary object is avoided (12.8), 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 11), shall be satisfied. ]

You can get the effect you want by avoiding copy-initialization and using direct-initialization:

 const A &b(B());  

Note:

Since newer versions of GCC apparently have a different behavior, I thought I'd post this note, which might address the difference (with both behaviors still standards conforming):

8.5.3/5 References says:

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 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 to a 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.

I originally read the last sentence ("the constructor that would be used...") to apply to both options, but maybe it should be read as only applying to the seconds option - or at least maybe that's how the GCC maintainers are reading it.

I'm not sure if this is what's going on between the differing behavior of GCC versions (comments welcome). We're definitely reaching the limits of my language-lawyering skills...

like image 24
Michael Burr Avatar answered Nov 06 '22 17:11

Michael Burr


I think it is indeed a compiler bug, gcc seems to think that is is copy initialization. Use direct initialization instead:

const A& b(B());

The copy-constructor call in copy initialization is always optimized away (an instance of copy elision), and then doesn't have to be available.

like image 43
Philipp Avatar answered Nov 06 '22 16:11

Philipp