The wording which confuses me is this:
It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with static storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized. It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.
In practice, this means that:
int do_throw() { throw 0; }
int x = do_throw(); // It is implementation-defined whether an exception is
// thrown before main() or inside main().
int main() try {
// an exception could be thrown here because we aren't using main()
// (main can't be used, see [basic.start.main])
return x;
} catch (...) {}
Note: merely defining main isn't odr-use of the function, see [basic.def.odr] p8. This makes it valid to sequence do_throw() inside main.
If the initialization is deferred, and the odr-use of x inside main triggers dynamic initialization inside main, it would be reasonable to believe that the function-try-block around main catches the exception. However:
Exceptions thrown in [...] or in constructors of objects associated with non-block variables with static storage duration are not caught by a function-try-block on the main function.
Contrary to intuition, the function-try-block on main would not catch the exception, even if the dynamic initialization of x is deferred.
Questions
Is this an oversight? Is it intentional? Do try blocks ignore deferred dynamic initializaton in general, or is it just the function-try-block on main?
How would you even implement this?! The function-try-block on main would have to selectively ignore some exceptions that originate from deferred dynamic initialization, but catch exceptions from other sources.
Further thoughts
The implied behavior is extremely surprising:
int main() try {
throw 0; // safe
} catch (...) {}
int main() try {
return x; // std::terminate is called because of an uncaught exception ?!?!
} catch (...) {}
int main() {
try {
return x; // safe ?!
} catch(...) {}
}
Note: the try block is first statement inside main, so if deferring takes place, return x cannot be sequenced before try. I'm not sure if this is even implementable, because main only has one statement (unless we consider all statements recursively).
It is irrelevant, because an exception thrown from the initialization of a non-block static storage duration variable always calls
std::terminateaccording to [basic.start.dynamic]/8. You can't catch it anywhere anyway.The only concern would be that
std::terminatemight be called at any unspecified point inmainprior to the first odr-use and in any thread.