C++ Logo

std-proposals

Advanced search

Re-purposing the function exception specification

From: <andrei_at_[hidden]>
Date: Sat, 20 Jun 2020 15:10:53 +0100
In some situations I want checked exceptions (ALMOST Java style) to make
sure thay are handled.
I also want all my existing C++ code to keep working without any changes.
I also want the use of "checked" exceptions to be a choice, not an
enforced practice.

What if we re-purpose the "<function header> throw(list-of-types)"
syntax to mean "these exceptions
are checked FOR THAT FUNCTION", i.e. make the list-of-checked-exceptions
a property of a function
(method, function type) - an exception class by itself is not "checked"
or "unchecked":

============================================================
class base_ex : public std::exception { ... }
class derived_ex : public base_ex { ... }

void foo() throws() { ... } // no exceptions thrown at all
(== noexcept(true))
void zee() { ... } // may throw anything, no
exceptions are "checked"
void bla() throws (base_ex) { ... } // may throw anything, also
"base_ex" is "checked"
void umf() throws (derived_ex) { ... } // may throw anything, also
"derived_ex" is "checked"

void (*pfoo1)() throws() = foo; // OK
void (*pfoo2)() throws() = zee; // Not OK, "zee" may throw but
"*pfoo2" must not
void (*pfoo3)() throws() = bla; // Not OK, "bla" may throw but
"*pfoo3" must not
void (*pfoo4)() throws() = umf; // Not OK, "umf" may throw but
"*pfoo4" must not

void (*pzee1)() = foo; // OK, "*pzee1" may throw, but "foo" will never
do so
void (*pzee2)() = zee; // OK
void (*pzee3)() = bla; // Not OK, "*pzee3" has no "checked"
exceptions, but "bla" has a "checked base_ex"
void (*pzee4)() = umf; // Not OK, "*pzee4" has no "checked"
exceptions, but "umf" has a "checked derived_ex"

void (*pbla1)() throws(base_ex) = foo; // OK, "*pbla1" has a "checked
base_ex", but "foo" will never throw it
void (*pbla2)() throws(base_ex) = zee; // OK, "*pbla2" has a "checked
base_ex", and "zee" may or may not throw it
void (*pbla3)() throws(base_ex) = bla; // OK
void (*pbla4)() throws(base_ex) = umf; // OK, "umf" has a "checked
derived_ex" but "*pbla4" has checked "base_ex"

void (*pumf1)() throws(derived_ex) = foo; // OK, "*pumf1" has a
"checked derived_ex", but "foo" will never throw it
void (*pumf2)() throws(derived_ex) = zee; // OK, "*pumf2" has a
"checked derivede_ex", and "zee" may or may not throw it
void (*pumf3)() throws(derived_ex) = bla; // Not OK, "*pumf" has a
"checked derived_ex" but "bla" has checked "base_ex"
void (*pumf4)() throws(derived_ex) = umf; // OK

void huh1() { bla(); } // Not OK, the "checked
base_ex" of "bla" is not handled or propagated
void huh2() throw(base_ex) { umf(); } // OK, the "checked
derived_ex" of "umf" is propagated out of "huh2"
void huh3() { try { umf() } catch (const base_ex &) {}; } // OK, the
"checked derived_ex" is handled by "catch"
============================================================

The old "pound the function senseless if it throws something non on its
throw(...) list" has been
removed as of C++17 (I think), so there's no harm to any code written in
C++17-or-later.

Any pre-C++11 code that uses the old-style "throw(...)" semantics will
still keep working, because
when e.g. a "g++" is called the C++ version is specified explicitly with
e.g. "-std=c++17".

What do you think?

Thanks in advance,
     Andrey Kapustin
     andrei_at_[hidden]

Received on 2020-06-20 09:14:06