Topic: Formal spec for the proposed additonal exception handling construct
Author: alf_p_steinbach@yahoo.no.invalid (Alf P. Steinbach)
Date: Fri, 20 Sep 2002 12:03:04 +0000 (UTC) Raw View
This is an amendment to the original formal specification, concerning:
* Vague description: of XInfoObject function.
It was not 100% clear that the equivalence scheme required one
such function per user-supplied exception-info class.
* Oversight: specificity ordering within XInfoObject function.
I forgot to state the requirement of specificity ordering for
the automatically generated catch-clauses.
Changes as follows:
======================================================================
On Wed, 18 Sep 2002 19:36:01 +0000 (UTC), alf_p_steinbach@yahoo.no.invalid (Alf P.
Steinbach) wrote:
>3.3 "fallback_clause" equivalence.
>
>...
>For retrieval of exception information, let XInfoObject be an
>on-the-fly generated function with a unique name,
>
> static xinfo_class_name* XInfoObject()
> ...
<replacement-text>
For retrieval of exception information in an object of class
XInfo, let XInfoObject<XInfo> be a generated function,
template<>
XInfo* XInfoObject<XInfo>()
{
try{
throw;
} catch( T1 const& x ) {
return new XInfo( x );
} catch( T2 const& x ) {
return new XInfo( x );
// etc.
} catch( ... ) {
struct NoType{};
return new XInfo( NoType() );
}
}
</replacement-text>
>where T1, T2 etc. correspond to the types the constructors of
>the user-supplied xinfo_class_name type can take as single
>arguments, and the "..." catch is only generated when a "..."
>constructor exists. The effect of this function is to either
>return an exception info object, or else rethrow the exception.
<replacement-text>
The effect of this function is to either return an exception info
object, or else rethrow the exception.
The types T1, T2 etc. correspond to the types the constructors of
the user-supplied XInfo class can take as single arguments (this
ensures that they must be distinct types). For any pair of such
types Ta and Tb, if Ta is a visible base class of Tb, then Tb (the
most specific class) must appear before Ta in the list of catch
clauses. Visibility means that at the point of call to XInfoObject,
given a hypotethical pointer pb to a Tb object, the expression
dynamic_cast<Ta const*>(pb) would be non-null.
The "..." catch is only generated when a "..." constructor exists.
</replacement-text>
======================================================================
Base-class first in catch-clauses is one more "inadvertent error"
that this construct helps programmers to avoid, simply by automating
the task -- and avoiding redundancy (the same code in different
catch-clauses) to boot.
I think the construct must be a good idea from Platon-land: much more
nice properties have emerged than I tried to put in in the first place.
Cheers,
- Alf
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alf_p_steinbach@yahoo.no.invalid (Alf P. Steinbach)
Date: Sat, 21 Sep 2002 18:13:06 +0000 (UTC) Raw View
This is a second amendment to the original formal specification, concerning:
* Oversight: closed door for solution of nested exception problem.
I forgot to limit exception info classes to constructors of one
argument only (except "..."), so that a later extension to handle
exceptions during stack unwinding isn't prohibited (if such an
extension were to rely on more than one constructor argument).
* Oversight: templated constructor not meaningful for exception info.
I forgot to explicitly disallow one-argument constructors with
templated argument type, although it's obvious , and I stated this
xinfo class requirement in the comp.lang.c++.moderated discussion.
Changes as follows:
======================================================================
On Wed, 18 Sep 2002 19:36:01 +0000 (UTC), alf_p_steinbach@yahoo.no.invalid (Alf P.
Steinbach) wrote:
>...
>2 NEAR-SYNTACTICAL CONSTRAINTS
>
> ...
>
> c5 -- Availability requirement for xinfo_class_name.
> There must exist an available constructor, an available
> destructor and some type T such that declaring a variable
> of type xinfo_class_name at the point immediately before
> the succeed_or_throw_block, using one constructor argument
> of type T, is valid.
<replacement-text>
c5.A -- Availability requirement for xinfo_class_name.
There must exist an available non-templated constructor, an
available destructor and some type T such that declaring a
variable of type xinfo_class_name at the point immediately
before the succeed_or_throw_block, using one constructor
argumentof type T, is valid.
</replacement-text>
<inserted-new-text>
c5.B -- Single arg requirement for xinfo_class_name.
In addition to the one-argument constructors in c5.A the
xinfo_class_name class may have an available default constructor
that has no defaulted arguments, and it may have an available
generic constructor of the form "xinfo_class_name( ... )". It
may not have any other available constructor that takes two or
more arguments.
</inserted-new-text>
======================================================================
Renumbering of the text will have to have to wait...
Cheers,
- Alf
) Perhaps it's not so obvious, so if not, here's an explanation. In
current C++ template type arguments are unrestricted, so with an argument
arg of template type T one might write, e.g., "arg.singalong()" with no
complaint from the compiler. In order to match an exception object to T
the compiler would have to check that "singalong()" etc. were supported,
and generate the constructor and any other called templated functions
on-the-fly, at run-time, which just isn't on. Although invoking the
compiler at run-time and doing a full compilation might do the trick...
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alf_p_steinbach@yahoo.no.invalid (Alf P. Steinbach)
Date: Wed, 18 Sep 2002 19:36:01 +0000 (UTC) Raw View
A FORMAL DEFINITION OF THE "SUCCEED_OR_THROW" CONSTRUCT.
This formal definition is posted only to [comp.std.c++].
[comp.lang.c++.moderated] has been removed in order to reduce
the lag-time for responses.
The formal definition is meant to form a basis for concrete,
informed discussion (perhaps of changes), and to show
(1) That the construct can be efficiently implemented, at
least as efficiently as try-catch.
(2) That the construct can be implemented without changing
the exception handling mechanism of current C++, in
particular without changing the meaning of throwing.
(3) That the general construct's equivalence to a hierarchy
of nested try-catch'es resolves all issues regarding
interaction with try-catch and throwing.
I'd like to present an actual implementation, but discovered
that modififying the g++ compiler required more work than I'm
willing to put in just to produce a proof-of-concept (I
implemented the "succeed_or_throw{ statements... }" part, but
without checking for disallowed control statements etc.). So
this formal definition isn't one that's been verified by
actual implementation. It might contain errors or oversights.
The construct defined here is an extension and slight
modification of the original proposal, based on the ensuing
discussion (which unfortunately was mostly limited to
comp.lang.c++.moderated). Short recap: it is a *structured*
construct that aims to help the programmer ensure that a
piece of code either succeeds or else throws. It does this
by making success explicit and failure implicit, and by
removing the need for nested try-catch'es for fallbacks.
But first, reiteration:
>META DISCUSSION ISSUES
>
>In contrast to the earlier [comp.lang.c++.moderated]
>idea this proposal involves new keywords.
>
>To avoid this thread degenerating into a debate about
>new keywords (i.e., how difficult the keyword problem
>is to solve), I suggest
>
>
> * Please start a new thread if you feel like tackling
> the topic of how to (not) introduce new keywords.
>
> * In this thread, pro- and counter-arguments ASSUME
> some solution to the problem of new keywords.
>
>
>In other words, much like discussing spacesuit design
>before anyone had entered space -- but in the hope or
>assurance that that little practical problem ;-) would
>be conquered.
=====================================================================
1 SYNTAX
In the following modified BNF "+" denotes a line continuation.
Quoted text denotes literals. The special word "nothing",
unquoted, denotes nothing (used to indicate optional things).
succeed_or_throw_block:
succeed_or_throw_clause +
(fallback_clauses | nothing) +
(cleanup_clause | nothing)
succeed_or_throw_clause:
"succeed_or_throw" compound_stmt
fallback_clauses:
fallback_clause | fallback_clauses fallback_clause
fallback_clause:
"fallback_to" (xinfo_object_decl | nothing) compound_stmt
cleanup_clause:
"clean_up" compound_stmt
xinfo_object_decl:
"(" xinfo_class_name xinfo_name ")"
xinfo_class_name:
identifier // Name of class with certain properties.
xinfo_name:
identifier // Variable scoped in following compound_stmt.
succeed_stmt:
"succeed" (return_stmt | ";")
(The succeed_stmt sans embedded return_stmt transfers control to
the point directly after the entire succeed_or_throw_block.
With an embedded return_stmt the succeed_stmt transfers control
out of the function. The semantics are then as return_stmt.)
2 NEAR-SYNTACTICAL CONSTRAINTS
Constraints that conceptually are at the syntactical level, but
which cannot be (efficiently) expressed in e.g. BNF:
c1 -- Placement of succeed_stmt.
A succeed_stmt is only valid within a succeed_or_throw_clause and
within a fallback_clause. It is not valid within a cleanup_clause.
In particular, it is not valid outside a succeed_or_throw_block.
c2 -- Obligatory succeed_stmt.
Every succeed_or_throw_clause and fallback_clause must contain at
least one potentially reachable succeed_stmt.
c3 -- Validity of return_stmt in succeed_stmt.
A succeed_stmt with an embedded return_stmt is invalid if the
embedded return_stmt is invalid. For example, in non-void
functions other than main an embedded return statement must
specify a return value compatible with the function's type.
c4 -- Form of xinfo_class_name.
This can be a direct or a qualified class name.
c5 -- Availability requirement for xinfo_class_name.
There must exist an available constructor, an available
destructor and some type T such that declaring a variable
of type xinfo_class_name at the point immediately before
the succeed_or_throw_block, using one constructor argument
of type T, is valid.
c6 -- Non-throwing requirement for xinfo_class_name.
The destructor must be non-throwing. Constructors may
throw. However, with a possibly throwing constructor the
class isn't much worth as an exception-info class.
c7 -- Transfer-of-control limitations.
The only explicit statements that are allowed to transfer
control out of or into a clause in a succeed_or_throw_block
are (1) succeed_stmt and (2) any throw statement, including
pure "throw;". In particular, the statements "break",
"continue" and "goto" are not allowed to transfer control
out of or (for "goto") into any clause of the construct.
Direct use of the C "longjmp" function is also disallowed
within a succeed_or_throw_block.
3 SEMANTICS -- BY EQUIVALENCE TO CURRENT C++.
In the following equivalence specifications the equivalent
code for some syntactical entity only shows how a compiler
might implement the construct. The constraints listed in
section 2 applies so that, e.g., the equivalent code for
succeed_stmt (below) is forbidden as actual user code.
Also, this equivalence scheme doesn't imply that an actual
implementation would have to work like this; it only has
to provide the equivalent functionality.
3.1 "succeed_stmt" equivalence.
Let success_label be an imaginary label placed directly
after the succeed_or_throw_block. Then:
Form 1:
"succeed;"
Equivalence:
"goto" success_label ";"
Form 2:
"succeed" return_stmt
Equivalence:
return_stmt
There are two reasons for requiring use of the word "succeed"
for a simple function return in this context. First, to
make success explicit; that you can only pass control out of
the construct in the case of success or failure (which must
use an exception). Second, to make it easier to check the
requirement that every succeed_or_throw_clause and every
fallback_clause must contain at least one potentially
reachable succeed_stmt.
3.2 "succeed_or_throw_clause" equivalence.
Let Form 1 be a succeed_or_throw_clause that isn't followed
by any fallback_clause(s) or cleanup_clause.
Let Form 2 be the case where the succeed_or_throw_clause is
followed by one or more fallback_clause(s) and/or a
cleanup_clause. In this case, let TheRest denote the
following fallback_clause(s) and/or cleanup_clause.
Differentiating between the two forms is only necessary in
order to avoid mostly unreachable rethrows everywhere.
The equivalence for Form 2 could handle it all, but then
a "throw;" would have to be added before the final "}".
And since the earlier debate indicated that this could be
difficult to understand I've tried to avoid obviously
unreachable code here -- for good or for worse...
Form 1:
"succeed_or_throw"
compound_stmt
Equivalence:
"{"
compound_stmt
"throw std::exception();"
"}"
Form 2:
"succeed_or_throw"
compound_stmt
TheRest
Equivalence:
"try {"
compound_stmt
"throw std::exception();"
"} catch(...) {"
$EquivalenceCodeFor<<TheRest>>
"}"
The line "$EquivalenceCodeFor<<TheRest>>" denotes exactly
what you might think, namely equivalence code for TheRest.
The $ sign is used to make this notation distinct from
C++ code. Ditto for << and >>.
Note, for the following equivalence of fallback_clause and
cleanup_clause, that in the equivalence they're always inside
a generic "catch(...)", and so can rethrow the exception.
3.3 "fallback_clause" equivalence.
Let TheRest denote the fallback_clause(s) and/or cleanup_clause
that directly follows the given fallback_clause. In the forms
where TheRest exists it cannot be empty. As before this
distinction is simply to avoid unfruitful debate about
unreachable code and so on; the equivalence scheme would (I think)
be far more elegant without such a distinction.
For retrieval of exception information, let XInfoObject be an
on-the-fly generated function with a unique name,
static xinfo_class_name* XInfoObject()
{
try{
throw;
} catch( T1 const& x ) {
return new xinfo_class_name( x );
} catch( T2 const& x ) {
return new xinfo_class_name( x );
// etc.
} catch( ... ) {
struct NoType{};
return new xinfo_class_name( NoType() );
}
}
where T1, T2 etc. correspond to the types the constructors of
the user-supplied xinfo_class_name type can take as single
arguments, and the "..." catch is only generated when a "..."
constructor exists. The effect of this function is to either
return an exception info object, or else rethrow the exception.
XInfoObject() is for exposition only; a compiler would *not*
need to dynamically allocate the xinfo_class_name object.
Nor would a compiler need to use std::auto_ptr, used below for
exposition so that anyone who wants can try this out.
Form 1:
"fallback_to"
compound_stmt
Equivalence:
"{"
compound_stmt
"throw;"
"}"
Form 1.1:
"fallback_to(" xinfo_class_name xinfo_name ")"
compound_stmt
Equivalence:
"{"
std::auto_ptr<xinfo_class_name> __pInfo( XInfoObject() );
xinfo_class_name& x_info_name = *__pInfo.get();
compound_stmt
"throw;"
"}"
Form 2:
"fallback_to"
compound_stmt
TheRest
Equivalence:
"try {"
compound_stmt
"throw;"
"} catch( ... ) {"
$EquivalenceCodeFor<<TheRest>>
"}"
Form 2.1:
"fallback_to(" xinfo_class_name xinfo_name ")"
compound_stmt
TheRest
Equivalence:
"try {"
std::auto_ptr<xinfo_class_name> __pInfo( XInfoObject() );
xinfo_class_name& x_info_name = *__pInfo.get();
compound_stmt
"throw;"
"} catch( ... ) {"
$EquivalenceCodeFor<<TheRest>>
"}"
3.4 "cleanup_clause" equivalence.
Form 1:
"clean_up"
compound_stmt
Equivalence:
"{"
compound_stmt
"throw;"
"}"
3.5 Practical difference between fallback and cleanup.
In the equivalence scheme form 1 of "fallback_clause" is identical
to the only form of "cleanup_clause".
However, in a fallback clause the compiler enforces that there must
be at least one potentially reachable succeed_stmt, so a fallback
clause will ideally not throw -- it only throws on failure of
the contained code.
In a cleanup clause the compiler enforces that there is no
succeed_stmt at all, and so this clause will always end with
throwing an exception -- whether via some explicit throw statement,
or by implicitly rethrowing the caught exception.
=====================================================================
Additional point, not mentioned so far: what about succeed-or-throw
corresponding to a function try-block?
Cheers,
- Alf
(I have no peer review of this before posting, so errors may remain...)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]