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

1

10

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?

Axum answered 6/8, 2016 at 8:1 Comment(3)
Guaranteed copy elision will work with the list initialization scenario only for aggregates, because of the special rule for aggregates initialized with an object of the same type in C++17.. But the remaining cases seem not to be covered by guaranteed copy elision. But remember that you can simply drop the braces and it will work. I see no reason to put braces here.Compliment
Note that not even non-guaranteed copy elision will work here (afaics.. this is not an initialization, but a specific application of overload resolution and a call of a constructor.. so it should not be covered by the elision rules unless explicitly stated), so it's not an issue introduced by C++17.Compliment
I posted question to std discussion: groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/…Compliment
D
7

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.

Durware answered 6/8, 2016 at 14:46 Comment(4)
Well, I just didn't see any proper usage of guaranteed copy elision in list-initialization by writing something like return {prvalue};, but I'm not sure about that. It seems that there is no need to guarantee copy elision in list-initialization, and that's why the standard draft doesn't specify it?Axum
@Carousel: I'm not sure how much more clearly I can say this. Elision is based on expressions. Braced-init-lists are not expressions. Therefore, elision does not apply to them.Durware
@NicolBolas well, they are not expressions, just like the (foo) in T t(foo) is not an expression and the = foo in T t = foo. However, foo is one, just like prvalue is one within {prvalue}. So saying up-front "initializer-lists are not expressions, therefore there is no copy elision in list-initialization" doesn't seem sound to me. In fact, some forms of it permit copy elision as I have shown in my comment above.Compliment
BTW is this not a contradiction in the wording? "a prvalue that is used to compute the value of an operand of an operator or that has type (possibly cv-qualified) void has no result object. [ Note: Except when the prvalue is the operand of a decltype-specifier, a prvalue of class or array type always has a result object".Compliment

© 2022 - 2024 — McMap. All rights reserved.