views:

261

answers:

2

The following code only works when the copy constructor is available.

When I add print statements (via std::cout) and make the copy constructor available it is not used (I assume there is so compiler trick happening to remove the unnecessary copy).

But in both the output operator << and the function plop() below (where I create a temporary object) I don't see the need for the copy constructor. Can somebody explain why the language needs it when I am passing everything by const reference (or what I am doing wrong).

#include <iostream>

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

std::ostream& operator<<(std::ostream& str,N const& data)
{
    return str << "N\n";
}

void plop(std::ostream& str,N const& data)
{
    str << "N\n";
}

int main()
{
    std::cout << N(1);     // Needs copy constructor  (line 25)
    plop(std::cout,N(1));  // Needs copy constructor

    N    a(5);
    std::cout << a;
    plop(std::cout,a);
}

Compiler:

[Alpha:~/X] myork% g++ -v
Using built-in specs.
Target: i686-apple-darwin10
Configured with: /var/tmp/gcc/gcc-5646~6/src/configure --disable-checking --enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1 --program-prefix=i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686-apple-darwin10
Thread model: posix
gcc version 4.2.1 (Apple Inc. build 5646)

[Alpha:~/X] myork% g++ t.cpp
t.cpp: In function ‘int main()’:
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:25: error: within this context
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:26: error: within this context

This is a simplified version of some real code.
In the real code I have a class that contains a std::auto_ptr. This means that a copy constructor that takes a const reference is not valid (without some work) and I was getting an error indicating that the copy constructor was not available because of it:

Change the class too:

class N
{
    public:
        N(int)  {}
    private:
        std::auto_ptr<int>  data;
};

The error is then:

t.cpp:25: error: no matching function for call to ‘N::N(N)’

+9  A: 

From http://gcc.gnu.org/gcc-3.4/changes.html

When binding an rvalue of class type to a reference, the copy constructor of the class must be accessible. For instance, consider the following code:

class A 
{
public:
  A();

private:
  A(const A&);   // private copy ctor
};

A makeA(void);
void foo(const A&);

void bar(void)
{
  foo(A());       // error, copy ctor is not accessible
  foo(makeA());   // error, copy ctor is not accessible

  A a1;
  foo(a1);        // OK, a1 is a lvalue
}

This might be surprising at first sight, especially since most popular compilers do not correctly implement this rule (further details).

This will be fixed in C++1x by Core Issue 391.

dalle
I can see the need for a copy constructor for the statement 'foo(makeA());' as it is the result of copying the result back from the function makeA() (even if the compiler elimiates the actualy copy it should be there). But the statement 'foo(A())' should not require a copy (apart from the standard requireing it) which I hope Issue 391 is designed to resolve.
Martin York
+3  A: 

The applicable parts of the standard here are §8.5.3/5, which covers initialization of references and §3.10/6, which tells what's an rvalue and what's an lvalue (not always obvious in C++).

In this case, you initialization expression is: "N(1)", so you're explicitly creating an object using functional notation. According to 3.10/6, that expression is an rvalue.

Then we have to walk through the rules in 8.5.3/5 in order, and use the first that applies. The first possibility is if the expression represents an lvalue, or can be implicitly converted to an lvalue. Your expression is an rvalue, and implicit conversion to an lvalue would require a conversion function that returns a reference, which doesn't seem to exist in this case, so that doesn't seem to apply.

The next rule says the reference must be to a const T (which is the case here). In this case, the expression is an rvalue of class type and is reference-compatible with the reference (i.e. the reference is to the same class, or a base of the class). That means the bullet at the bottom of page 151 (179 of the C++ 2003 PDF) seems to apply. In this case, the compiler is allowed to either bind the reference directly to the object representing the rvalue, OR create a temporary copy of the rvalue, and bind to that temporary copy.

Either way, however, the standard explicitly requires that: "The constructor that would be used to make the copy shall be callable whether or not the copy is actually done."

As such, I believe that gcc is right to give an error message, and the others are technically wrong to accept the code. I simplified your code a bit to the following:

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

void plop(N const& data) { }

int main() {
    plop(N(1));
}

When invoked with "--A" (strict errors mode), Comeau gives the following error message:

"plop.cpp", line 12: error: "N::N(const N &)", required for copy that was
          eliminated, is inaccessible
      plop(N(1));
           ^

Likewise, when invoked with "/Za" (its "ANSI conforming" mode), VC++ 9 gives:

plop.cpp
plop.cpp(12) : error C2248: 'N::N' : cannot access private member declared in class 'N'
        plop.cpp(6) : see declaration of 'N::N'
        plop.cpp(2) : see declaration of 'N'
        while checking that elided copy-constructor 'N::N(const N &)' is callable
        plop.cpp(6) : see declaration of 'N::N'
        when converting from 'N' to 'const N &'

My guess is that most of the other compilers do roughly the same. Since they optimize out the call to the copy constructor, they don't normally require that it exist or be accessible. When you ask them to conform to the standard as accurately as they can, they give the error message, because it's technically required even though they don't use it.

Jerry Coffin
Is there actually a copy made here 'plop(N(1));'? If there is I don't see it as the compiler is allowed to bind the reference directly to the object representing the rvalue. The OR part of the conversation is thus not required and thus copy is not required to be made. Unfortunately it is the next paragraph (that forces a conforming compiler to produce an error) that hurts me.
Martin York
@Martin: I can hardly imagine a compiler that would really make a copy, even with its most minimal level of optimization (though of course it *is* barely possible). The silver lining is that most (i.e. sensible) compilers don't normally enforce it, and with C++ 0x, the requirement will be gone.
Jerry Coffin