Carousel Carousel - 3 months ago 29
C++ Question

How does guaranteed copy elision work in list-initialization in C++1z?

There is a paragraph about guaranteed copy elision in c++ draft n4606 [dcl.init] 17.6:



  • If the destination type is a (possibly cv-qualified) class type:


    • If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example:
      T x = T(T(T()));
      calls the
      T
      default constructor to initialize
      x
      . — end example ]

    • [...]





There is also a Q&A talks about how it works.

To myself understanding, the rule I quoted guarantees that no ctors should get involved when initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination. So that no needs to check the existence of copy or move ctor, which makes following codes to be legal in C++17:

struct A {
A() {}
A(A const &) = delete;
A(A &&) = delete;
};
A f() { return A(); } // it's illegal in C++14, and suppose to be legal in C++17


However, what drives me crazy is I can't find similar rules in list-initialization section in c++ draft n4606. What I found is ([dcl.init.list] 3.6)


[...]


  • Otherwise, if
    T
    is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
    [...]




Since list-initialization has higher priority than the first rule I quoted, we should consider the rule in list-initialized section when the initializer is a initializer-list. As we can see, constructors are considered when list-initialize a class type
T
. So, continued to the previous example, will

A ff() { return {A()}; }


be legal in C++17? And can someone find where the standard draft specify how does guaranteed copy elision work in list-initialization?

Answer

Guaranteed elision works by redefining prvalue expressions to mean "will initialize an object". They don't construct temporaries anymore; temporaries are instead constructed by certain uses of prvalue expressions.

Please note the frequent use of the word "expression" above. I point that out because of one very important fact: a braced-init-list is not an expression. The standard is very clear about this. It is not an expression, and only expressions can be prvalues.

Indeed, consider the section of the standard on elision:

This elision of copy/move operations, called copy elision, is permitted in the following circumstances:

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object...
  • ...
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type

These all involve expressions (temporary class objects are expressions). Braced-init-lists aren't expressions.

As such, if you issue return {anything};, the construction of the return value from anything will not be elided, regardless of what anything is. According to the standard, of course; compilers may differ due to bugs.

Now that being said, if you have a prvalue expression of the same type as the return value, you are highly unlikely to want to type return {prvalue}; instead of just return prvalue;. And if the expression was of a different type, then it doesn't qualify for elision anyway.

Comments