5

I created this base class for non-copyable classes:

class non_copyable
{
public:

    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;

    virtual ~non_copyable() = default;

protected:

    non_copyable() = default;
};

Then I created this derived class:

class manager
    : public non_copyable
{
public:
    manager()
    {
    }

    std::string s;
};

I'm able to create an instance of the class and return it like this:

manager get()
{
    return manager();
}

I think this should not be possible, because the copy constructor is deleted and the implicitly generated move constructor is deleted, because there is a user-defined (deleted) copy constructor.

This code compiles with MinGW-64 7.2 but not with MSVC 2017 and yields this message:

function "manager::operator=(const manager &) throw()" (declared implicitly) cannot be referenced -- it is a deleted function

Is this a problem with the compiler, allowed by C++ design or am I doing something wrong?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
lorisleitner
  • 688
  • 1
  • 5
  • 20

3 Answers3

6

In C++17, this operation requires neither a move nor a copy; the entire thing is elided.

As such, Visual Studio is either wrong or incomplete in its implementation of this language standard.

In general try not to alter C++'s own semantics. Preventing expensive things is fine, but preventing free things is a step too far IMO.

Depending on your exact version, this blog post may be relevant — it states that they tried to make this feature work, but it was too broken, so they rolled it back for now; I do not know whether there is a more recent version that implements it.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
4

As of C++17, we have guaranteed copy elision. This means that returning a value from a function doesn't require a copy or a move.

Unfortunately, Visual Studio doesn't yet support this:

P0135R1 Guaranteed copy elision No

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Justin
  • 24,288
  • 12
  • 92
  • 142
4

In prvalue expressions are not logically objects. In they are, logically, objects (or rather they create them).

Now, prvalue expressions are instructions on how to make an object. In certain circumstances those instructions are applied to create temporary or non-temporary objects.

This is more popularly known as "guaranteed elision". But really it just removes any need of elision in many cases (not all).

manager get() {
  return manager();
}

In manager() was a prvalue expression that creates an object. The return value is another prvalue. The copy or move from manager() to the return value of get could be elided by these two objects having their identity and lifetime merged.

In manager() is a prvalue, as is the return value of get(). You don't "copy" or "move" instructions on how to create an object, and neither are objects. The return just tells the return value "here are the instructions you need".

manager foo = get();

here we construct foo from a prvalue -- from instructions on how to make a manager. No temporary object is created; rather, we just construct the object as instructed by the prvalue return of get().

In we'd instead have a temporary manager object whose lifetime could be elided with the named object foo.

The effects of this kind of elision and using prvalues "directly" are very similar at runtime, but one involves logical move or copy constructor calls we later eliminate, the other one never had a 2nd object to begin with.

Asto why MSVC2017 acts differently, their implementation of remains incomplete (in admittedly smaller ways each year, but I still get burned) let alone .

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • This is interesting information. What the reference in the Standard to "... prvalue expressions are instructions on how to make an object ..."? Thanks! –  Mar 12 '18 at 22:03
  • @shankar basically in the C++17 wording that separates the prvalue expression from the temporary or other object it initializes. Until used to initialize something that isn't the prvalue, there is no object in the prvalue. In comparison, there was an object in C++14, it just had its existence usually elided away. The cases where it did not, C++17 materializes a temporary then uses the prvalue to intialize the temporary... – Yakk - Adam Nevraumont Mar 12 '18 at 22:42
  • 1
    @Shankar Some refs: in C++14’s [basic.lval] §1.4-5 it is established that a prvalue is a temporary (sub)object or an objectless value. In C++17’s [basic.lval] §1.2 a prvalue becomes "an expression whose evaluation initializes an object […], as specified by the context in which it appears"—it does not stand for the object itself any more, as clarified immediately below in the new wording in §2: "The *result* of a prvalue is the value that the expression stores into its context. […] The *result object* of a prvalue is the object initialized by the prvalue[…]". – Luc Danton Mar 13 '18 at 00:59