Is a copy constructor required when returning by implicit conversion?
Asked Answered
K

3

22

The following code compiles fine in Visual C++ 2013, but not under GCC or Clang.

Which is correct?
Is an accessible copy constructor required when returning an object via an implicit conversion?

class Noncopyable
{
    Noncopyable(Noncopyable const &);
public:
    Noncopyable(int = 0) { }
};

Noncopyable foo() { return 0; }

int main()
{
    foo();
    return 0;
}

GCC:

error: 'Noncopyable::Noncopyable(const Noncopyable&)' is private
  Noncopyable(Noncopyable const &);
  ^
error: within this context
 Noncopyable foo() { return 0; }

Clang:

error: calling a private constructor of class 'Noncopyable'
Noncopyable foo() { return 0; }
                    ^
note: implicitly declared private here
        Noncopyable(Noncopyable const &);
        ^
warning: C++98 requires an accessible copy constructor for class 'Noncopyable' when binding a reference to a temporary; was private [-Wbind-to-temporary-copy]
Noncopyable foo() { return 0; }
                           ^
note: implicitly declared private here
        Noncopyable(Noncopyable const &);
        ^
Kerosene answered 16/6, 2014 at 7:43 Comment(4)
Nice question, however by default, I vote for gcc/clang as the true implementation of C++ rather than MSVC.Rossie
@MM.: Thanks :) yeah, that's typically true. In this case I feel it would make sense to say a copy constructor isn't required though, that's mainly why I asked. There isn't any object apparent that requires copying (even in the absence of copy elision).Kerosene
possible duplicate of Copy Constructor Needed with temp objectFurfuran
@Furfuran that looks like a different question.Breedlove
H
15

When you return an expression, a temporary object of the return type is created, initialised with that expression, and then moved (or copied, if moving is not an option), into the return value. So you need an accessible copy or move constructor.

It is however possible to initialise the return value directly, by using a braced list. So the following works:

Noncopyable foo() { return {0}; }

Similar case in live example.

Healall answered 16/6, 2014 at 8:10 Comment(5)
+1 this is really cool, but I don't get it: doesn't that also require a conversion from initializer_list?Kerosene
@Mehrdad No. Braces used like this do not produce an initializer_list, they produce a braced-init-list (a nonterminal of the grammar). Loosely speaking, it's the syntax for "in-place initialisation." Only if the class has an initializer_list constructor is this interpreted as "create an initializer_list and initialise with it."Healall
Interesting, did not realize that. Thanks!Kerosene
Nice answer, but do you have an explanation for why this works, or a quote from the standard?Crumpton
@Crumpton Like [stmt.return]/2 ? "A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list."Sungkiang
B
5

12.8 Copying and moving class objects [class.copy]

1/ A class object can be copied or moved in two ways: by initialization (12.1, 8.5), including for function argument passing (5.2.2) and for function value return (6.6.3); [...]

In 6.6.3 The return statement [stmt.return]:

2/ [...] The value of the expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy or move of a temporary object (12.2) [...]

and 12.2 Temporary objects [class.temporary]:

1/ Temporaries of class type are created in various contexts: binding a reference to a prvalue (8.5.3), returning a prvalue (6.6.3), a conversion that creates a prvalue (4.1, 5.2.9, 5.2.11, 5.4), [...] Note: even if there is no call to the destructor or copy/move constructor, all the semantic restrictions, such as accessibility (Clause 11) and whether the function is deleted (8.4.3), shall be satisfied. [...]

I'd argue that GCC and clang are correct - I'd even go as far as saying that any time you return by value, the return type has to have an accessible copy or move constructor.

The logic would be that a temporary is created to convert the original type to the new type (int to Noncopyable), and afterwards a copy of that temporary is made to return for the function.

It's essentialy the same as:

Noncopyable foo() { return Noncopyable(0); }

Would you expect a copy to be required there? I certainly would.

Breedlove answered 16/6, 2014 at 8:0 Comment(0)
F
4
  • Function foo returns a Noncopyable object by value. Thus, theoretically a copy constructor must be evoked.

  • If you make the copy constructor available (i.e., public) and print a message to flag its evocation, you'll see that this message is not printed DEMO and only the overloaded conversion operator is evoked.

  • This is due to the fact of the copy elision optimization.

  • Thus, is not that the overloaded conversion operator requires a copy constructor, but rather the return statement of foo requires a copy constructor because you return by value.

  • Eventually, the copy constructor is not going to be evoked due to copy elision but still must be available.

Fowl answered 16/6, 2014 at 8:11 Comment(1)
Have an upvote for being the only one to actually mention copy elision/RVO.Pederast

© 2022 - 2024 — McMap. All rights reserved.