Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some unclear wording in [except] CWG2775 #5238

Closed
xmh0511 opened this issue Jan 25, 2022 · 10 comments
Closed

Some unclear wording in [except] CWG2775 #5238

xmh0511 opened this issue Jan 25, 2022 · 10 comments
Labels
cwg Issue must be reviewed by CWG. not-editorial Issue is not deemed editorial; the editorial issue is kept open for tracking.

Comments

@xmh0511
Copy link
Contributor

xmh0511 commented Jan 25, 2022

[except.throw] p3

Throwing an exception copy-initializes ([dcl.init], [class.copy.ctor]) a temporary object, called the exception object.

The sentence does not specify the source of the copy-initialization. A possible improvement maybe that

Throwing an exception copy-initializes ([dcl.init], [class.copy.ctor]) a temporary object, called the exception object, from the operand of the throw-expression.

[except.throw] p2

When an exception is thrown, control is transferred to the nearest handler with a matching type ([except.handle]); “nearest” means the handler for which the compound-statement or ctor-initializer following the try keyword was most recently entered by the thread of control and not yet exited.

Is the following an improvement to the definition of "nearest handler"?

“nearest” means the handler of a try-block or function-try-block whose compound-statement or ctor-initializer was most recently entered by the thread of control and not yet exited.

The improvement seems to be simpler to read.

[except.throw] p5

When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided ([class.copy.elision]). The destructor is potentially invoked ([class.dtor]).

"thrown object" seems not to be a formal wording. Because we have defined exception object. Isn't that better to be used instead of "thrown object"?

@frederick-vs-ja
Copy link
Contributor

I think the thrown object means the operand of the throw-expression. As the exception object is the target of initialization, it's meaningless whether it's considered as an lvalue.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 26, 2022

I think the thrown object means the operand of the throw-expression. As the exception object is the target of initialization, it's meaningless whether it's considered as an lvalue.

The operand of the throw-expression is an expression that does not necessarily denote an object, it can be a prvalue of a certain type, the result of which is not an object. The sentence

the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue

should be rephrased into two parts

  • the constructor selected for the copy-initialization of the exception object
  • the constructor selected for a copy-initialization of the variable declared by the exception-declaration considering the thrown object exception object as an lvalue

The latter is consistent with [except.handle] p14. The lvalue purposes to otherwise specify the category of the initializer expression when copy-initializing the target where "a temporary object" would have been denoted by an xvalue in common cases.

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Jan 26, 2022

The first occurrence of "thrown object" in [except.throw] has been present since C++98, and the second occurrence is introduced by CWG1863 (adopted at Oct. 2015). Both occurrences are older than the adoption of P0135R1 (at Jun. 2016) which makes prvalues of classes and arrays not objects.

What I meant is the first occurrence of "thrown object" might correctly mean the operand until C++17/P0135R1, but is incorrect now. It seems that the second occurrence of "thrown object" is wrong at first, because it is the exception object, whose type is never cv-qualified, that is needed to be copied, while the type of the operand of a throw-expression may be cv-qualified.

Since C++20/P0848R3, a copy/move constructor may be constrained, so there may be no such constructor "selected for the copy-initialization", in which case the program is presumably ill-formed. But it seems that the current wording does not cover such cases.

I want to use the following wording:

Let t be the operand of the throw-expression, VT be the type of the exception object. For invented variables e and ec, the variable definitions VT e = t; and VT ec = e; shall be well-formed in the context of the throw-expression , even if the copy/move operation that would be used to create the exception object is elided.

It seems that the current usage of "accessible" is also problematic, as the initialization and the copy of the exception object may happen in contexts with different access.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 27, 2022

what do e and ec denote respectively, in your wording?

It seems that the current usage of "accessible" is also problematic, as the initialization and the copy of the exception object may happen in contexts with different access.

"accessible" is checked in the context of the first matched handler, except that the exception is rethrown, "accessible" will continue to be checked for the context of the first matched handler with which the rethrown exception matches, and so on.

@frederick-vs-ja
Copy link
Contributor

e and ec are not things. They are not created, and are only needed to check the validity of copy-initialization.

"Accessible" in [except.throw] p5 may refer to different contexts, and possibly two different constructors.

  • The context where the throw-expression appears. The first constructor is checked.
  • The context of the first matched handler. The second constructor is checked.
  • The context inside std::current_exception. The second constructor is checked.
    • However, even though a std::current_exception call may only copy the exception object when it's nested within some matched handler, the std::current_exception function itself has no access to private constructors.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 27, 2022

In general, the two constructors that need to be checked refer to them:

  • the constructor selected for constructing the exception object from the operand, which is the first one you refer to.
  • the constructor selected for copying the exception object, which is the second one you refer to.

It seems that rethrow_exception and make_exception_ptr are also contexts where needs to check the corresponding constructor. It could augment your proposal to

Let E be the operand of the throw-expression, T be the type of the exception object. If the copy/move operation elision is not considered, for invented variables, v and vc:

  • the variable definition T v = E; shall be well-formed in any context where constructs an exception object,
  • T vc = v; shall be well-formed in any context where copies an exception object,

However, this proposal would ignore a case that the target object of the initialization has a base class type B of T and the copy/move constructor of B is inaccessible at that point.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 27, 2022

the confusion use of exceptions and exception objects.

[propagation] p1 states

The type exception_­ptr can be used to refer to an exception object.

The same meaning is also appeared in [propagation] p10, [except.throw] p4.2. However, [propagation] p8 instead says that

An exception_­ptr object that refers to the currently handled exception or a copy of the currently handled exception

The currently handled exception is an exception rather than an exception object. We should clarify the relationship between an exception and the exception object.

@frederick-vs-ja
Copy link
Contributor

However, this proposal would ignore a case that the target object of the initialization has a base class type B of T and the copy/move constructor of B is inaccessible at that point.

You're right. There can be three different constructors involved.

Given CWG2711 has made some clarification, the first two cases are addressed, and there's arguably no accessibility issue - it might be implied that accessibility check is made in the context of the throw-expression or the handler (which is also clang's current behavior).

The case for std::current_exception is still problematic. Currently, no mainstream implementation performs the necessary check, and it's even unclear whether the source of copy-initialization needed for std::current_exception is a cv-unqualified lvalue.

@jensmaurer
Copy link
Member

We need to "capture" the (or a) copy constructor at the point of the "throw" (which may or may not be a throw-expression), because current_exception and similar functions operate on type-erased exceptions, thus we can't perform any overload resolution or accessibility checks at that point.

It seems to make sense to use a "const lvalue" for the source of the copy.

@jensmaurer jensmaurer added cwg Issue must be reviewed by CWG. not-editorial Issue is not deemed editorial; the editorial issue is kept open for tracking. labels Jun 2, 2023
@frederick-vs-ja
Copy link
Contributor

Should be already fixed by #6906 (c37fc84).

@jensmaurer jensmaurer changed the title Some unclear wording in [except] Some unclear wording in [except] CWG2775 Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cwg Issue must be reviewed by CWG. not-editorial Issue is not deemed editorial; the editorial issue is kept open for tracking.
Projects
None yet
Development

No branches or pull requests

3 participants