C++0x/1x exception specifications proposal: Compile-time checked

I

Ioannis Vranos

Perhaps a mechanism can be introduced in the C++0x/1x standard,
something simple like defining a function as:


void somefunc(void) throw()
{
// ...
}


and getting a compile time error saying something like:


"Error: void somefunc(void) throw(): Wrong exception specification.
somefunc can throw std::bad_alloc, std::range_error".

That is make the compiler to check exception specifications for errors too.



Ioannis A. Vranos
 
J

Jerry Coffin

Perhaps a mechanism can be introduced in the C++0x/1x standard,
something simple like defining a function as:

void somefunc(void) throw()

[ ... ]
and getting a compile time error saying something like:

"Error: void somefunc(void) throw(): Wrong exception specification.
somefunc can throw std::bad_alloc, std::range_error".

That is make the compiler to check exception specifications for errors too.

This is next to impossible in practice. In particular, the compiler
doesn't have anything to check against with externally defined functions
-- i.e. you include a header with an exception specification, but the
compiler has no access to the implementation of the "stuff" in that
header (unless it happens to be included inline).

In practice, doing so probably wouldn't be a good idea anyway. Java has
checked exceptions, where the compiler does (more or less) what you're
advocating. Experience has shown (repeatedly) that while this seems like
a good idea, it almost never works well in practice.

In the end, exception specifications are an anti-pattern -- something
that initially seems like a good idea, but in the long run ends up
causing far more problems than it solves.

One of the basic ideas of exception handling is that it sets up more or
less a "tunnel" from the place an exception is thrown to the place it is
caught, and any intermediate layers need not worry about it (beyond
being exception safe). Exception specifications (especially enforced as
you're advocating) require that all intermediate levels DO pay attention
to the exceptions that propagate from the lower levels, though the
intermediate layer has no real interaction with the exception itself at
all. Worse, an intermediate layer might easily work with a function via
a pointer, in which case it simply has no way of knowing what exceptions
might be thrown through it by that function. You could make the
exception specification part of the function signature, so it could only
take pointers to functions that threw from a limited selection of
exceptions, but this would frequently be quite limiting. In many cases,
such call-back functions (and such) haven't even been contemplated when
the code that uses them is written, so it's essentially impossible for
them to even guess at what exceptions they might throw.

In addition, templates and exception specifications are essentially
impossible to blend. A typical template can throw most (if not all) the
exceptions that can be thrown by the type over which it is instantiated
-- but an many cases, nobody has yet imagined the type over which it
will be isntantiated when the template itself is written.

I'll repeat: exception specifications are an anti-pattern. They're
nearly always bad now, and having the compiler attempt to enforce them
would make them even worse.
 
E

Erik Wikström

Perhaps a mechanism can be introduced in the C++0x/1x standard,
something simple like defining a function as:


void somefunc(void) throw()
{
// ...
}


and getting a compile time error saying something like:


"Error: void somefunc(void) throw(): Wrong exception specification.
somefunc can throw std::bad_alloc, std::range_error".

That is make the compiler to check exception specifications for errors too.

You might be interested in the following two articles (just a few among
many discussing the subject):

http://herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!149.entry
http://www.mindview.net/Etc/Discussions/CheckedExceptions
 
I

Ioannis Vranos

Ioannis said:
Perhaps a mechanism can be introduced in the C++0x/1x standard,
something simple like defining a function as:


void somefunc(void) throw()
{
// ...
}


and getting a compile time error saying something like:


"Error: void somefunc(void) throw(): Wrong exception specification.
somefunc can throw std::bad_alloc, std::range_error".

That is make the compiler to check exception specifications for errors too.


Some more details:


What I have in mind is "loose" application. That is, the compiler should
check only the available code accessible to it.


In addition, I think throw(void) should be equivalent to throw(), and
throw(...) should be equivalent to non-throw specification, e.g.:


// May throw anything.
void somefunc()
{
// ...
}


// May throw anything.
void some func() throw(...)
{
// ...
}


I think that even loose, the compile time checking of throw
specifications will improve the overall exception safety, and everyone
will usually know the exact types of exceptions he can catch on a given
call.


Any existing third-party, pre-built C++ library can be revised to
provide exception specifications at its next release. That is, in the
future the total exception safety will increase, because the exception
specification mechanism will finally work (since now I think it doesn't
work as it is provided, in practice).

Or that library can keep providing its facilities without exception
specifications or with the throw(...) equivalent ones.


If I may repeat, what I have in mind is "loose" application. That is,
the compiler should check only the available code accessible to it.


In the case of some function having the throw() specification, is using
indirectly code that may throw some exception, it can be flagged as a
compile-time error, provided that this source code that is used
indirectly by this function, is accessible to the compiler. Otherwise,
it will not be flagged as a compile-time error.

The run-time stuff of the throw specifications will still apply.
 
I

Ioannis Vranos

Ioannis said:
Some more details:


What I have in mind is "loose" application. That is, the compiler should
check only the available code accessible to it.


In addition, I think throw(void) should be equivalent to throw(), and
throw(...) should be equivalent to non-throw specification, e.g.:


// May throw anything.
void somefunc()
{
// ...
}


// May throw anything.
void some func() throw(...)
{
// ...
}


I think that even loose, the compile time checking of throw
specifications will improve the overall exception safety, and everyone
will usually know the exact types of exceptions he can catch on a given
call.


Any existing third-party, pre-built C++ library can be revised to
provide exception specifications at its next release. That is, in the
future the total exception safety will increase, because the exception
specification mechanism will finally work (since now I think it doesn't
work as it is provided, in practice).

Or that library can keep providing its facilities without exception
specifications or with the throw(...) equivalent ones.


If I may repeat, what I have in mind is "loose" application. That is,
the compiler should check only the available code accessible to it.


In the case of some function having the throw() specification, is using
indirectly code that may throw some exception, it can be flagged as a
compile-time error, provided that this source code that is used
indirectly by this function, is accessible to the compiler. Otherwise,
it will not be flagged as a compile-time error.

The run-time stuff of the throw specifications will still apply.


Addition 2:

In this proposal, I think an opposite of the throw keyword may be
needed, to specify the exceptions that the compiler will ignore. I think
the right place of it, is the end of the function scope. An example:


void somefunc() throw()
{
std::vector<int> vec;

// ...

} nothrow(std::eek:ut_of_range)
 
I

Ioannis Vranos

Ioannis said:
Addition 2:

In this proposal, I think an opposite of the throw keyword may be
needed, to specify the exceptions that the compiler will ignore. I think
the right place of it, is the end of the function scope. An example:


void somefunc() throw()
{
std::vector<int> vec;

// ...

} nothrow(std::eek:ut_of_range)



Some examples of these:


I.

void somefunc() throw(std::bad_alloc, my_app::graph_range_error)
{
// ...
} nothrow(std::eek:ut_of_range)



II. Equivalent cases:

void somefunc() throw()
{
// ...
} nothrow(std::bad_alloc)


void somefunc() throw(void)
{
// ...
} nothrow(std::bad_alloc)



III. Equivalent cases:

void somefunc()
{
// ...
}

void somefunc() throw(...)
{
// ...
}

void somefunc() throw(...)
{
// ...
} nothrow()


void somefunc()
{
//...
} nothrow()


void somefunc() throw(...)
{
// ...
} nothrow(void)


void somefunc()
{
//...
} nothrow(void)
 
I

Ioannis Vranos

Ioannis said:
Some examples of these:


I.

void somefunc() throw(std::bad_alloc, my_app::graph_range_error)
{
// ...
} nothrow(std::eek:ut_of_range)



II. Equivalent cases:

void somefunc() throw()
{
// ...
} nothrow(std::bad_alloc)


void somefunc() throw(void)
{
// ...
} nothrow(std::bad_alloc)



III. Equivalent cases:

void somefunc()
{
// ...
}

void somefunc() throw(...)
{
// ...
}

void somefunc() throw(...)
{
// ...
} nothrow()


void somefunc()
{
//...
} nothrow()


void somefunc() throw(...)
{
// ...
} nothrow(void)


void somefunc()
{
//...
} nothrow(void)


Addition 3:


IV.

// The compiler ignores all exceptions and considers that somefunc()
// will not throw any exception. Thus it will not provide a compile-time
// error.

void somefunc() throw()
{
throw std::bad_alloc();
} nothrow(...)
 
A

Andrew Koenig

Perhaps a mechanism can be introduced in the C++0x/1x standard, something
simple like defining a function as:
void somefunc(void) throw()
{
// ...
}
and getting a compile time error saying something like:
"Error: void somefunc(void) throw(): Wrong exception specification.
somefunc can throw std::bad_alloc, std::range_error".

The problem with this is that *any* expression that has the capability of
causing undefined behavior has the capability of throwing any exception. In
particular, this property means that an implementation is permitted to
extend the language so that arithmetic errors, such as division by zero,
throw exceptions.

What would you have such an implementation do about an arithmetic operation
inside a throw() function that might overflow? If the compiler complains
about it, it is rejecting a program that might have nothing wrong with it.
If it doesn't, then you need to figure out how to change your requirement to
permit such behavior.
 
E

Erik Wikström

Ioannis Vranos wrote:

[snip]

Could you please choose one thread for this discussion? Posting the same
thing in multiple threads makes the discussion hard to follow. And it
is a waste of bits :)
 
I

Ioannis Vranos

Erik said:
Ioannis Vranos wrote:

[snip]

Could you please choose one thread for this discussion? Posting the same
thing in multiple threads makes the discussion hard to follow. And it
is a waste of bits :)


In my newsgroup reader (Thunderbird) the message appears in the same
discussion thread, with subject "C++0x/1x exception specifications
proposal: Compile-time checked".
 
E

Erik Wikström

Erik said:
Ioannis Vranos wrote:

[snip]

Could you please choose one thread for this discussion? Posting the same
thing in multiple threads makes the discussion hard to follow. And it
is a waste of bits :)


In my newsgroup reader (Thunderbird) the message appears in the same
discussion thread, with subject "C++0x/1x exception specifications
proposal: Compile-time checked".

In my they do not, the messages are posted both in the thread "Bjarne's
comments about exception specification" and "C++0x/1x exception
specifications proposal: Compile-time checked". Google groups also see
two threads [1]. I notice that you are running Thunderbird 1.5, might be
some bug fixed in later versions.

1.
http://groups.google.com/group/comp...?lnk=gst&q=bjarne's+comments#9cb2f509b3527039
http://groups.google.com/group/comp...oposal:+Compile-time+checked#35b5716c348bae12
 
I

Ioannis Vranos

Erik said:
In my newsgroup reader (Thunderbird) the message appears in the same
discussion thread, with subject "C++0x/1x exception specifications
proposal: Compile-time checked".

In my they do not, the messages are posted both in the thread "Bjarne's
comments about exception specification" and "C++0x/1x exception
specifications proposal: Compile-time checked". Google groups also see
two threads [1]. I notice that you are running Thunderbird 1.5, might be
some bug fixed in later versions.

1.
http://groups.google.com/group/comp...?lnk=gst&q=bjarne's+comments#9cb2f509b3527039
http://groups.google.com/group/comp...oposal:+Compile-time+checked#35b5716c348bae12


No, you are right on this. I indeed opened a new specific discussion
thread about my proposal so as to gather more feedback.
 
I

Ioannis Vranos

A revised version of my proposal (version 1.9):


Design ideals:

1. *Loose* compile-time checking of throw compile-time specifications.
The compiler reports an error if it can detect one. If it can't detect
one, the compiler does not report any error. That implies, it is up to
the implementation on how much it will try to detect such errors, apart
from the obvious cases.

2. Current C++03 code is not affected by this proposal. The default is
no compile-time checking is done, if no throw compile-time specification
is provided.

3. Compile-time checking of throw compile-time specifications is done
separately from throw run-time checking. That is, "throw" specifications
remain as they are, run-time checked. New keywords are introduced for
compile-time checking. There probably can be prettier names, but here I
choose the keywords "_throw" and "_nothrow" for this purpose.



The above imply that:

1. The loose compile-time checking of _throw/_nothrow specifications is
defined explicitly by the programmer if he chooses to do so. The default
is no compile-time checking.

2. If an unexpected exception is thrown, it propagates as it didn't
violate the compile-time _throw/_nothrow specifications, that is with no
run-time side-effects. In other words, if the compile-time
exception-specification is violated at run-time, std::unexpected() is
*not* invoked.

3. The loose compile time checking will eventually lead to better error
handling code, that is more safe code without any additional run-time cost.



Details:

1. The possible uses and meanings of "_throw" are:

_throw(), _throw(void): The function or member function does not throw
any exception.

_throw(some_exception1, some_exception2): The function or member
function may throw one of those exceptions. It may throw more that are
defined in the _nothrow specification, but does not report them as a
compile-time error (loose meaning).

_throw(...): Equivalent to non-existence of compile-time _throw
specification. That is:


void somefunc()
{
// ...
}

is equivalent to

void somefunc() _throw(...)
{
// ...
}

_throw(...) is just more explicit.



2. The possible uses and meanings of "_nothrow" are:

_nothrow(), _nothrow(void): Equivalent to non-existence of compile-time
_nothrow specification. That is the following are equivalent:


void somefunc()
{
// ...
}


void somefunc()
{
// ...
} _nothrow()


void somefunc()
{
// ...
} _nothrow(void)



_nothrow(some_exception1, some_exception2): The compiler will ignore
some_exception1 and some_exception2 and will not provide any
compile-time error, if it detects that any of these two violate any
_throw specification.


Example:

void somefunc() _throw(std::bad_alloc, my_app::graph_range_error)
{
// ...
} _nothrow(std::eek:ut_of_range)



3. _nothrow(...): Ignore all exceptions. Compatible only with the
_throw() specification.


Example:

void somefunc() throw()
{
// ...
} nothrow(...)
 
I

Ioannis Vranos

Added a clarification:


Ioannis said:
A revised version of my proposal (version 1.9):


Design ideals:

1. *Loose* compile-time checking of throw compile-time specifications.
The compiler reports an error if it can detect one. If it can't detect
one, the compiler does not report any error. That implies, it is up to
the implementation on how much it will try to detect such errors, apart
from the obvious cases.

2. Current C++03 code is not affected by this proposal. The default is
no compile-time checking is done, if no throw compile-time specification
is provided.

3. Compile-time checking of throw compile-time specifications is done
separately from throw run-time checking. That is, "throw" specifications
remain as they are, run-time checked. New keywords are introduced for
compile-time checking. There probably can be prettier names, but here I
choose the keywords "_throw" and "_nothrow" for this purpose.


4. The compile-time checking is *loose*, and will issue a compile-time
error, only if it detects that a *specific* exception can be thrown that
violates the _throw/_nothrow specifications, and will not be based on
declaration hierarchy.



The above imply that:

1. The loose compile-time checking of _throw/_nothrow specifications is
defined explicitly by the programmer if he chooses to do so. The default
is no compile-time checking.

2. If an unexpected exception is thrown, it propagates as it didn't
violate the compile-time _throw/_nothrow specifications, that is with no
run-time side-effects. In other words, if the compile-time
exception-specification is violated at run-time, std::unexpected() is
*not* invoked.

3. The loose compile time checking will eventually lead to better error
handling code, that is more safe code without any additional run-time cost.



Details:

1. The possible uses and meanings of "_throw" are:

_throw(), _throw(void): The function or member function does not throw
any exception.

_throw(some_exception1, some_exception2): The function or member
function may throw one of those exceptions. It may throw more that are
defined in the _nothrow specification, but does not report them as a
compile-time error (loose meaning).

_throw(...): Equivalent to non-existence of compile-time _throw
specification. That is:


void somefunc()
{
// ...
}

is equivalent to

void somefunc() _throw(...)
{
// ...
}

_throw(...) is just more explicit.



2. The possible uses and meanings of "_nothrow" are:

_nothrow(), _nothrow(void): Equivalent to non-existence of compile-time
_nothrow specification. That is the following are equivalent:


void somefunc()
{
// ...
}


void somefunc()
{
// ...
} _nothrow()


void somefunc()
{
// ...
} _nothrow(void)



_nothrow(some_exception1, some_exception2): The compiler will ignore
some_exception1 and some_exception2 and will not provide any
compile-time error, if it detects that any of these two violate any
_throw specification.


Example:

void somefunc() _throw(std::bad_alloc, my_app::graph_range_error)
{
// ...
} _nothrow(std::eek:ut_of_range)



3. _nothrow(...): Ignore all exceptions. Compatible only with the
_throw() specification.


Example:

void somefunc() _throw()
{
// ...
} _nothrow(...)
 
G

Grizlyk

Ioannis said:
void somefunc(void) throw()

"Error: void somefunc(void) throw(): Wrong exception specification.
somefunc can throw std::bad_alloc, std::range_error".

That is make the compiler to check exception specifications
for errors too.

As i know, folks time by time offer the idea. The checking is
possible, of course, but will reqiure changes of many properties of C+
+ exception handling paradigm, maybe properties of C++ core.

Unfortunately, there are many useful changes if C++, that can not be
resolved by std library, so all of the ideas go away :).

For your "void somefunc(void) throw()" declaration.

In the example, you are using declaration of "throw(type)" as logical
one, to tell programmers about expected types of exception. And
compiler, or course, easy can do compile tests according declarations
of all used function, as compiler do it for all parameters.

In the case of exception "Warning:" can be used instead of "Error:".

But you can guess, that exception handling require runtime overhead in
comparison with ordinary C-style function, but some function never
will throw, so they could be implemented without the overhead.

The C++ declaration "throw()" do the same - define the kind of
function implementation rather than declare logical type of
exception.

The overhead is the fact, that implementation of exception handling
_logically_ require from function _two_ separated returns - with
exception and without. The requirement _logically_ means, that
external context must pass to the function two addresses: ordinary
return point and exceptional return point.

But if the C-style function "somefunc throw()" does throw, the
exception can not be passed via boundaries of "somefunc", due to
absence of the _logically_ second return address - exceptional return
point, means caller is not ready to process exceptions. To call
"unexpected()" is only way to continue or it is better to say "only
way to exit".

I hope you have surprised, as if you find
void somefunc(void) virtual() inline() throw();
declaration :). I want to say, that this is unexpected place of
linkage type keywords :)

One can see, that there are two types of exception declaration:
logical and hardware(linkage).

Declaration:

[hardware (linkage)]
[type]
[function name]
[(]
[list of parameters]
[)]
[logical]
[;]

hardware: virtual, inline, nothrow, extern "", stdcall, register, etc
logical: throw(type), const, etc

is best solution.

examples:

inline nothrow
void
somefunc(); //throw() is default here by nothrow

Here exit with unexpected() if somefunc throw, there is no hardware
overhead.

inline
void
somefunc(void)throw();

Here can add/replace throw std::unexpected_exception if somefunc
throw, there is hardware overhead, but process is reliable for
exceptions.

inline
void
somefunc(void)throw(my_type&);

Here can add/replace throw std::unexpected_exception if somefunc throw
unrelated to my_type, there is hardware overhead, but process is
reliable for exceptions.

One of the problem is necessity of C++ stacked exceptions instead of
single exception. In the case we must not replace original exception
with std::unexpected_exception instead of add the
std::unexpected_exception for original exception.

Maksim A. Polyanin
http://grizlyk1.narod.ru/cpp_new
 
G

Grizlyk

Andrew said:
The problem with this is that *any* expression that
has the capability of causing undefined behavior
has the capability of throwing any exception.  

And reliable code must be able to catch all of them with catch(...).
In particular, this property means that an implementation
is permitted to extend the language so that arithmetic
errors, such as division by zero, throw exceptions.

And it is very strange, why the trivial operations with POD types
still are not reliable, and errors for the operations can not be
catched by catch(...).

C language does not catch them, just because C language has no
exceptions.

The hardware exceptions are async, but automatically can be synced at
the point of synced usage of failed result (assume compiler must know
about possible exception with the operation for concrete arch) or with
stacked exceptions, error can be delayed to end of nearest try block.
What would you have such an implementation do about
an arithmetic operation inside a throw() function
that might overflow?  

If the compiler complains about it, it is rejecting a program
that might have nothing wrong with it.

Warnings can be used instead of errors, but programmer must be able to
explicitly express his desire to not throw or to throw or to throw
something concrete, and compiler must understand the desire.

Maksim A. Polyanin
http://grizlyk1.narod.ru/cpp_new
 
I

Ioannis Vranos

I think we are very close to finish it. I think We can make safe-solid
source code.


Please everyone to contribute, so as to make an excellent compile-time
checking exception specification mechanism.



Ioannis said:
Added a clarification:


Ioannis said:
A revised version of my proposal (version 1.9):


Design ideals:

1. *Loose* compile-time checking of throw compile-time specifications.
The compiler reports an error if it can detect one. If it can't detect
one, the compiler does not report any error. That implies, it is up to
the implementation on how much it will try to detect such errors,
apart from the obvious cases.

2. Current C++03 code is not affected by this proposal. The default is
no compile-time checking is done, if no throw compile-time
specification is provided.

3. Compile-time checking of throw compile-time specifications is done
separately from throw run-time checking. That is, "throw"
specifications remain as they are, run-time checked. New keywords are
introduced for compile-time checking. There probably can be prettier
names, but here I choose the keywords "_throw" and "_nothrow" for this
purpose.

[REMOVED]
4. The compile-time checking is *loose*, and will issue a compile-time
error, only if it detects that a *specific* exception can be thrown that
violates the _throw/_nothrow specifications, and will not be based on
declaration hierarchy.



The above imply that:

1. The loose compile-time checking of _throw/_nothrow specifications
is defined explicitly by the programmer if he chooses to do so. The
default is no compile-time checking.

2. If an unexpected exception is thrown, it propagates as it didn't
violate the compile-time _throw/_nothrow specifications, that is with
no run-time side-effects. In other words, if the compile-time
exception-specification is violated at run-time, std::unexpected() is
*not* invoked.

3. The loose compile time checking will eventually lead to better
error handling code, that is more safe code without any additional
run-time cost.



Details:

1. The possible uses and meanings of "_throw" are:

_throw(), _throw(void): The function or member function does not throw
any exception.

_throw(some_exception1, some_exception2): The function or member
function may throw one of those exceptions. It may throw more that are
defined in the _nothrow specification, but does not report them as a
compile-time error (loose meaning).

_throw(...): Equivalent to non-existence of compile-time _throw
specification. That is:


void somefunc()
{
// ...
}

is equivalent to

void somefunc() _throw(...)
{
// ...
}

_throw(...) is just more explicit.



2. The possible uses and meanings of "_nothrow" are:

_nothrow(), _nothrow(void): Equivalent to non-existence of
compile-time _nothrow specification. That is the following are
equivalent:


void somefunc()
{
// ...
}


void somefunc()
{
// ...
} _nothrow()


void somefunc()
{
// ...
} _nothrow(void)



_nothrow(some_exception1, some_exception2): The compiler will ignore
some_exception1 and some_exception2 and will not provide any
compile-time error, if it detects that any of these two violate any
_throw specification.


Example:

void somefunc() _throw(std::bad_alloc, my_app::graph_range_error)
{
// ...
} _nothrow(std::eek:ut_of_range)



3. _nothrow(...): Ignore all exceptions. Compatible only with the
_throw() specification.


Example:

void somefunc() _throw()
{
// ...
} _nothrow(...)


Things remaining to be solved:


void somefunc() _throw()
{
throw std::bad_alloc;
}

is probably an obvious compile time error case.


My current direction of thought in compile-time exception specifications:


The compile-time exception specifications of each function and member
function, are about declaring any *additional* exception the specific
function or member function will throw, and not about redeclaring the
"inherited" exceptions.


That is, each function/member function is a level, and each level
declares two things: The exceptions it can throw by itself using the
_throw keyword, and the "inherited"exceptions it can handle, that are
denoted with the _nothrow keyword.

At compile-time, those exception specifications are *accumulated*, and
at the caller level we specify (the caller function or member function),
we get a compile-time result of the exceptions that it can receive.


That is:

template <class T>
void somefunc(T &a) _throw()
{
// ...
}


Here somefunc() indicates that itself will not throw any additional
exceptions. One of its arguments may throw one, but itself will throw no
exception. That is, the compile-time exception specification of this
template function is correct.

At compile-time, those exception-specifications will be *accumulated* up
to the desired level of exception handling, where we will know the exact
types of exceptions we can handle.


Another example with a template:


template <class T>
void somefunc(T &a) _throw()
{
// ...
} _nothrow(std::bad_alloc)


This indicates that somefunc() will not throw any exceptions itself,
while it also handles the case of std::bad_alloc. This means
std::bad_alloc will stop being *accumulated* at the compile-time
exception checking.



==> So this is what it remains to be resolved:

How can we retrieve the accumulated exception types information, at a
desired level where we want to handle them, with compile-time messages?


Example:

void somefunc2() _throw(graph_exception)
{
// ...
}


void somefunc1() _throw(time_exception)
{
somefunc2();
} _nothrow(std::eek:ut_of_range)




// The question that remains to be solved is: How can we find out at the
// point of somefunc() call in main(), that we can receive
// time_exception and graph_exception, whose info has been *accumulated*
// at compile-time by the use of _throw/_nothrow compile-time
// specifications, so we can handle them there?

int main() try
{
somefunc1();
}

catch(time_exception)
{
// ...
}

catch(graph_exception)
{
// ...
}
 
I

Ioannis Vranos

Ioannis said:
My current direction of thought in compile-time exception specifications:


The compile-time exception specifications of each function and member
function, are about declaring any *additional* exception the specific
function or member function will throw, and not about redeclaring the
"inherited" exceptions.


That is, each function/member function is a level, and each level
declares two things: The exceptions it can throw by itself using the
_throw keyword, and the "inherited"exceptions it can handle, that are
denoted with the _nothrow keyword.

At compile-time, those exception specifications are *accumulated*, and
at the caller level we specify (the caller function or member function),
we get a compile-time result of the exceptions that it can receive.


That is:

template <class T>
void somefunc(T &a) _throw()
{
// ...
}


Here somefunc() indicates that itself will not throw any additional
exceptions. One of its arguments may throw one, but itself will throw no
exception. That is, the compile-time exception specification of this
template function is correct.

At compile-time, those exception-specifications will be *accumulated* up
to the desired level of exception handling, where we will know the exact
types of exceptions we can handle.


Another example with a template:


template <class T>
void somefunc(T &a) _throw()
{
// ...
} _nothrow(std::bad_alloc)


This indicates that somefunc() will not throw any exceptions itself,
while it also handles the case of std::bad_alloc. This means
std::bad_alloc will stop being *accumulated* at the compile-time
exception checking.



==> So this is what it remains to be resolved:

How can we retrieve the accumulated exception types information, at a
desired level where we want to handle them, with compile-time messages?


Example:

void somefunc2() _throw(graph_exception)
{
// ...
}


void somefunc1() _throw(time_exception)
{
somefunc2();
} _nothrow(std::eek:ut_of_range)




// The question that remains to be solved is: How can we find out at the
// point of somefunc() call in main(), that we can receive
// time_exception and graph_exception, whose info has been *accumulated*
// at compile-time by the use of _throw/_nothrow compile-time
// specifications, so we can handle them there?

int main() try
{
somefunc1();
}

catch(time_exception)
{
// ...
}

catch(graph_exception)
{
// ...
}


The solution that I propose on this, is the keyword :exceptions or
:_exceptions:


void somefunc2() _throw(graph_exception)
{
throw graph_exception();
}


void somefunc1() _throw(time_exception)
{

somefunc2();

throw time_exception();

} _nothrow(std::eek:ut_of_range)


int main()
{
somefunc1() :exceptions;
}



at compile-time it will produce a compiler message, something like:

"main()::somefunc1() may throw exceptions:
somefunc1()::somefunc2()::graph_exception, somefunc1::time_exception".


After we write our exception handlers, we can remove the keyword
":exceptions" from somefunc1(); statement in main(), and thus main becomes:


void somefunc2() _throw(graph_exception)
{
throw graph_exception();
}


void somefunc1() _throw(time_exception)
{

somefunc2();

throw time_exception();

} _nothrow(std::eek:ut_of_range)


int main() try
{
somefunc1();
}


catch(graph_exception)
{
// ...
}


catch(time_exception)
{
// ...
}
 
I

Ioannis Vranos

Ioannis said:
The solution that I propose on this, is the keyword :exceptions or
:_exceptions:


void somefunc2() _throw(graph_exception)
{
throw graph_exception();
}


void somefunc1() _throw(time_exception)
{

somefunc2();

throw time_exception();

} _nothrow(std::eek:ut_of_range)


int main()
{
somefunc1() :exceptions;
}



at compile-time it will produce a compiler message, something like:

"main()::somefunc1() may throw exceptions:
somefunc1()::somefunc2()::graph_exception, somefunc1::time_exception".


After we write our exception handlers, we can remove the keyword
":exceptions" from somefunc1(); statement in main(), and thus main becomes:


void somefunc2() _throw(graph_exception)
{
throw graph_exception();
}


void somefunc1() _throw(time_exception)
{

somefunc2();

throw time_exception();

} _nothrow(std::eek:ut_of_range)


int main() try
{
somefunc1();
}


catch(graph_exception)
{
// ...
}


catch(time_exception)
{
// ...
}



Compiler checks/errors:


Where the source code is available to the compiler, any
_nothrow(some_exception) specification at a function/member function,
must have at least one equivalent catch(some_exception) or catch(...)
exception handler at the function definition.

For example:


void somefunc() _throw()
{
int *p= new int[10];

} _nothrow (std::bad_alloc)


should be flagged as a compiler error, because there is no
catch(std::bad_alloc) or catch(...) exception handler at the function
definition.


The following should be correct:


1 .

void somefunc() try _throw()
{
int *p= new int[10]

} _nothrow (std::bad_alloc)


catch(std::bad_alloc)
{
// ...
}


2.

void somefunc() try _throw()
{
int *p= new int[10]

} _nothrow (std::bad_alloc)


catch(...)
{
// ...
}


3.

void somefunc() try _throw()
{
int *p= new int[10]

} _nothrow (...)


catch(...)
{
// ...
}
 
I

Ioannis Vranos

Ioannis said:
Compiler checks/errors:


Where the source code is available to the compiler, any
_nothrow(some_exception) specification at a function/member function,
must have at least one equivalent catch(some_exception) or catch(...)
exception handler at the function definition.

For example:


void somefunc() _throw()
{
int *p= new int[10];

} _nothrow (std::bad_alloc)


should be flagged as a compiler error, because there is no
catch(std::bad_alloc) or catch(...) exception handler at the function
definition.


The following should be correct:


1 .

void somefunc() try _throw()
{
int *p= new int[10]

} _nothrow (std::bad_alloc)


catch(std::bad_alloc)
{
// ...
}


2.

void somefunc() try _throw()
{
int *p= new int[10]

} _nothrow (std::bad_alloc)


catch(...)
{
// ...
}


3.

void somefunc() try _throw()
{
int *p= new int[10]

} _nothrow (...)


catch(...)
{
// ...
}



Where the source code is available to the compiler, any
_throw(some_exception) specification at a function/member function, must
have at least one equivalent throw some_exception(); statement.

Remember each function/member function is a level, and its _throw
specifications are about exceptions they explicitly throw, and not about
"inherited" exceptions from other function/member function calls.


For example:


void somefunc() _throw(std::bad_alloc)
{

}


is a compiler error.



void somefunc() _throw(std::bad_alloc)
{
throw std::bad_alloc();
}

is correct.



The following should be correct:


void somefunc() _throw()
{
vector<int> vec(10);

for (vector<int>::size_type i= 0; i< 10; ++i)
vec.at(i)=5;
}


because it doesn't throw any exception by itself.




void somefunc() try _throw()
{
vector<int> vec(10);

for (vector<int>::size_type i= 0; i< 10; ++i)
vec.at(i)=5;
} _nothrow (std::eek:ut_of_range)


catch(std::eek:ut_of_range)
{
// ...
}


is correct and removes std::eek:ut_of_range from the exception
"accumulation" list.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top