6

In §[except.throw], the standard says that throwing an exception copy-initializes the exception object from the throw expression

Throwing an exception copy-initializes (11.6, 15.8) a temporary object, called the exception object

Why then does the following code compile on C++17?

class Exception {
public:
   Exception() = default;
   Exception(Exception&&) = delete;
   Exception(const Exception&) = delete;
};

int main() {
    throw Exception{};
    return 0;
}

(https://wandbox.org/permlink/R3WfzfnBAORTLVSy)

Copy initialization does not include any case (from what it seems like to me) that is eligible for prvalue elision. Why then does the above code compile in C++17?

Curious
  • 20,870
  • 8
  • 61
  • 146
  • 2
    Are you confused why the copy constructor isn't used? Are you conflating copy initialization with copy construction? – xaxxon Aug 28 '18 at 00:51

1 Answers1

8

From a recent draft standard

11.6 [dcl.init]/15

The initialization that occurs in the form of a brace-or-equal-initializer or condition (9.4), as well as in argument passing, function return, throwing an exception (18.1), handling an exception (18.3), and aggregate member initialization (11.6.1), is called copy-initialization.

So T x = T(); is an example of copy-initialization. So is throwing an exception and many other cases.

What copy-initialization does is defined in other parts of 11.6 (along with other forms of initialization). The relevant section on prvalues in initializers is:

11.6 [dcl.init]/17.6.1

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

this also known as is guaranteed elision. If the initializer expression is a prvalue expression of matching type, the prvalue expression is used to directly construct the target of the initialization.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • How does this answer thw question? Are you saying T(T(T())) is an example of copy-initialization? – Curious Aug 28 '18 at 00:19
  • @Curious Extracted two quotes from draft standard instead of quotes from the proposal to edit the standard. – Yakk - Adam Nevraumont Aug 28 '18 at 00:31
  • Sorry. I still don't really follow. I guess this is telling me that my assumption of copy initialisation not being a viable candidate for elision might be wrong. But where does it say that a throw expression is a viable candidate for elision? – Curious Aug 28 '18 at 00:35
  • 1
    @Curious the first quote says that throwing an exception uses copy initialization. The second says that the initializer expression initializes the destination object. Therefor `Exception{}` initializes the object. I don't think there's really an elision, per se. – xaxxon Aug 28 '18 at 00:48
  • @Curious The quoted piece of [dcl.init] paragraph 17 applies to *any* direct-initialization or copy-initialization which initializes a possibly-cv-qualified class type object (not reference) from an expression of possibly-cv-qualified class type, if both types are the same class. There's no need to list all the applicable contexts. – aschepler Aug 28 '18 at 00:48
  • @xaxxon Yes, but it's also true that the nested initializer `{}` initializes its destination object - which is the exception object, since there is no other object associated with the `Exception{}` expression. – aschepler Aug 28 '18 at 00:50
  • Wait, no, `{}` isn't an expression. Hmm. – aschepler Aug 28 '18 at 01:02
  • @aschepler `Exception{}` is a prvalue expression of type `Exception`. When you copy-initialize an `Exception` with that, it is used to directly constructed from the prvalue expression. – Yakk - Adam Nevraumont Aug 28 '18 at 01:04
  • Agree, but I feel I'm missing some last quote to tie it all together. How do we know what it means to initialize a class object from a prvalue expression of the class's type, particularly when there are nested casts like in the Standard's example `T x = T(T(T()));`? – aschepler Aug 28 '18 at 01:12
  • 2
    Aha. The details in the [expr] clause for functional-style cast and `static_cast` (and therefore sometimes C-style cast) specify that if the expression is a class prvalue used to initialize a result object, the initializer or operand within the expression is used to initialize that result object, and those rules can apply recursively. – aschepler Aug 28 '18 at 01:29