Skip to content

Some unclear wording in [except] CWG2775 #5238

Description

@xmh0511

[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"?

Activity

  1. frederick-vs-ja commented on Jan 26, 2022

    @frederick-vs-ja
    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.

  2. xmh0511 commented on Jan 26, 2022

    @xmh0511
    ContributorAuthor

    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.

  3. frederick-vs-ja commented on Jan 26, 2022

    @frederick-vs-ja
    Contributor

    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.

  4. xmh0511 commented on Jan 27, 2022

    @xmh0511
    ContributorAuthor

    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.

  5. frederick-vs-ja commented on Jan 27, 2022

    @frederick-vs-ja
    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.
  6. xmh0511 commented on Jan 27, 2022

    @xmh0511
    ContributorAuthor

    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.

  7. xmh0511 commented on Jan 27, 2022

    @xmh0511
    ContributorAuthor

    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.

  8. frederick-vs-ja commented on May 30, 2023

    @frederick-vs-ja
    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.

  9. jensmaurer commented on Jun 2, 2023

    @jensmaurer
    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.

  10. added
    cwgIssue must be reviewed by CWG.
    not-editorialIssue is not deemed editorial; the editorial issue is kept open for tracking.
    on Jun 2, 2023
  11. frederick-vs-ja commented on Apr 17, 2024

    @frederick-vs-ja
    Contributor

    Should be already fixed by #6906 (c37fc84).

  12. changed the title [-]Some unclear wording in [except][/-] [+]Some unclear wording in [except] CWG2775[/+] on Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cwgIssue must be reviewed by CWG.not-editorialIssue is not deemed editorial; the editorial issue is kept open for tracking.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions