struct B {
B() throw();
B(const B&) = default; // implicit exception specification is noexcept(true)
B(B&&, int = (throw Y(), 0)) noexcept;
~B() noexcept(false);
};
int n = 7;
struct D : public A, public B {
int * p = new int[n];
// D::D() potentially-throwing, as the new operator may throw bad_alloc or bad_array_new_length
// D::D(const D&) non-throwing
// D::D(D&&) potentially-throwing, as the default argument for B's constructor may throw
// D:: D() potentially-throwing
};
Consider the above code which is cited from except.spec#11. I have no doubt to the exception specification of all constructors of D except for D::D(D&&), which obeys the following rule:
An implicitly-declared constructor for a class X, or a constructor without a noexcept-specifier that is defaulted on its first declaration, has a potentially-throwing exception specification if and only if any of the following constructs is potentially-throwing:
- a constructor selected by overload resolution in the implicit definition of the constructor for class X to initialize a potentially constructed subobject, or
- a subexpression of such an initialization, such as a default argument expression, or,
- for a default constructor, a default member initializer.
Obviously, The rule to make D::D(D&&) have a potentially-throwing exception specification is neither the first bullet nor the third bullet. For the first rule, the selected constructor to initialize base subobject of type B is B(B&&, int = (throw Y(), 0)) noexcept, which is declared to have a non-throwing exception specification. The third rule is for default constructor. So only the second rule applies to this case.
However, D::D(D&&) shouldn't have a potentially-throwing exception specification except that the expression throw Y() is considered as a subexpression of D::D(D&&).
The rule which define the immediate subexpression is as following:
The immediate subexpressions of an expression e are
- the constituent expressions of e's operands
- any function call that e implicitly invokes,
- if e is a lambda-expression, the initialization of the entities captured by copy and the constituent expressions of the initializer of the init-captures,
- if e is a function call or implicitly invokes a function, the constituent expressions of each default argument used in the call, or
- if e creates an aggregate object, the constituent expressions of each default member initializer ([class.mem]) used in the initialization.
A subexpression of an expression e is an immediate subexpression of e or a subexpression of an immediate subexpression of e.
The easy way to understand the rule for subexpression is that it works recursively, that is,
immediate subexpression of immediate subexpression... of immediate subexpression of e is a subexpression of e.
I agree the expression throw Y() is a subexpression of function B(B&&, int = (throw Y(), 0)) noexcept due to the fourth bullet. However, I don't know whether B(B&&, int = (throw Y(), 0)) noexcept is considered as an implicitly invoked function which is invoked by expression D::D(D&&), which seems to obey the second bullet. if it is that, please consider the following code:
class Test{
Test(){}
~Test(){}
};
void func(){
Test t{} // implicitly invoke the defautl constructor of `Test`
// would implicitly invoke the destructor of `Test`
}
int main(){
func();
}
So, as I write in the comment, Is the constructor and the destructor of Test is considered as subexpressions of expression func()? If it's not, how to interpret the wording a subexpression of such an initialization? So, my questions are:
Q1:
In the second example, Is the implicitly invoked constructor or destructor be considered as a subexpression of expression func()?
Q2:
If the answer to the first question is no, then how to interpret a subexpression of such an initialization?