5

Consider the following code:

#include<memory>

struct A {
    std::auto_ptr<int> i;
};

A F() {
    A a;
    return a;
}

int main(int argc, char **argv) {
    A a = F();
    return 0;
}

When compiling I receive a compilation error, (see here):

error: no matching function for call to ‘A::A(A)’
     A a = F();
             ^

To my understanding, A::A(A) isn't even allowed to exist, so why is the compiler requesting it? Secondly, why is it not using RVO?


If it is because a std::auto_ptr cannot be returned from a function, why does the following compile and run?

#include<memory>

std::auto_ptr<int> F() {
    std::auto_ptr<int> ap;
    return ap;
}

int main(int argc, char **argv) {
    std::auto_ptr<int> ap = F();
    return 0;
}

I cannot use C++11 in my current work unfortunately, hence the use of auto_ptr.

Cramer
  • 1,785
  • 1
  • 12
  • 20
  • 4
    RVO requires an accessible copy-constructor to be present, even if it's not used. – chris Jun 06 '14 at 01:56
  • 7
    AFAIK `std::auto_ptr` is a complete and unreliable mess. You are probably better off it. You can use `boost::unique_ptr` instead. – Shoe Jun 06 '14 at 01:56
  • @chris, isn't the default copy-constructor present? `std::auto_ptr` can be returned raw from a function. – Cramer Jun 06 '14 at 01:59
  • @Cramer, Keep in mind `std::auto_ptr`'s constructors do "moves" and consequently take a non-const reference. Your link's error tells you that `A` gets the same constructor: one that takes a non-const `A &`. `F()` cannot bind to `A &`. – chris Jun 06 '14 at 02:13
  • @chris, is `auto_ptr` non-copyable? Is this the reason why you need an explicit copy ctor? Because if you replace e.g. `std::auto_ptr i;` by some POD, then RVO works without the need of explicitly declaring a copy ctor. At least that's what I understand from pre-C++11 "moves". – vsoftco Jun 06 '14 at 02:15
  • 2
    @vsoftco, It's "copyable", but doesn't support the traditional `Class(const Class &)` copy-constructor because of how it implements the "copy". Thus, the class with that as a member cannot get a traditional default copy-constructor. – chris Jun 06 '14 at 02:18
  • @chris, yeah that's what I thought, was just editing my comment. Thanks! – vsoftco Jun 06 '14 at 02:19
  • @Cramer `std::shared_ptr` was introduced in the C++2003 tr1, even most older compilers have it although it may be in the `std::tr1::` namespace. – Mgetz Jun 07 '14 at 15:40

2 Answers2

1

I tried searching but couldn't find a relevant Q&A, even though I know this is a duplicate. So instead I'm answering instead of voting to close as a duplicate. Apologies.


The reason it needs a copy constructor is because the line:

A a = F();

is really (from the compiler's perspective):

A a(F());

even if copy elision/RVO is used. That is, the compiler does not do:

// This is NOT what the compiler does for A a = F();
A a;
a = F();

Even with copy elision/RVO, A a(F()); won't work. From a C++ standards perspective, the code needs to be legal, whether or not the compiler does copy elision. Copy elision doesn't relax the requirement of needing a copy constructor (even if it doesn't actually use it; it still needs to be there in order to ensure "legality" of the code).

This doesn't work, because std::auto_ptr's copy constructor doesn't take a const reference, so A's copy constructor doesn't exist. F() returns a temporary A, which can only be captured by a const reference, which means that line of code is trying to use a non-existent copy constructor.

Cornstalks
  • 37,137
  • 18
  • 79
  • 144
  • But then why then is `std::auto_ptr F()` legal? If you remove the class `A` and just pass the 'raw' `auto_ptr` around it never complains. Note it's not asking for `A(A&)`, its specifically asking for `A(A)`, which is non-sensical. If you implement `A(A&)` (which is implemented automatically if you see the error) it still won't compile. – Cramer Jun 06 '14 at 05:51
0

The compiler normally makes the default constructor

A(const A&)

However in this case it is not possible as there is no auto_ptr::auto_ptr(const auto_ptr &rhs) so it creates the following instead:

A(A&)

Now, when the F returns it will not let the return value a be modified (I think because it might be a persisting object such as a global or a reference). When it finds no A(const A&) it will instead look for A(A) as that's the only other way to return the value without modifying a (even though it's stupid). Even using RVO, it must still be valid code as @Cornstalks mentions in his answer (to an extent, see below).

auto_ptr gets around this by creating a temporary auto_ptr_ref object using the following (ref)

auto_ptr::operator auto_ptr_ref()
auto_ptr(auto_ptr_ref)

For some reason, the compiler accepts the typecast to auto_ptr_ref despite it not being a const function and rejecting the use of A(A&).


To get around this issue one can simply declare A(const A&) without implementing it

struct A {
    A() { }
    A(const A&);
    std::auto_ptr<int> i;
};

The compiler thinks that the return is legal but applies RVO before the linker sees it and so the missing implementation is never needed. Of course it precludes the use of

A a;
A b(a);

An inelegant fix, but effective none the less.

Cramer
  • 1,785
  • 1
  • 12
  • 20
  • @Cornstalks fixed, do you know why it's allowed to use the typecast but not the copy-constructor? – Cramer Jun 07 '14 at 12:26