C++ Logo

std-proposals

Advanced search

Re: Re-purposing the function exception specification

From: <andrei_at_[hidden]>
Date: Mon, 22 Jun 2020 23:12:47 +0100
Thanks again for the prompt and informative response.

I did not want to make "throw(auto)" the default because that would
defeat my stated goal - which was to make a specific exception either
"checked" or "unchecked" for a given function/method and not to make the
entire exception class "checked in any possible context" (which is what
Java does and would be disastrous for pre-existing C++ code base).

Cheers,

     Andrey Kapustin

On 22/06/2020 22:34, Arthur O'Dwyer wrote:
> On Mon, Jun 22, 2020 at 3:44 PM andrei_at_[hidden]
> <mailto:andrei_at_[hidden]> <andrei_at_[hidden]
> <mailto:andrei_at_[hidden]>> wrote:
>
> Thanks again for pointing to a dark corner in my proposal.
>
> For the template problem I can see at least 2 solutions (and would
> propose allowing both in the language).
>
> 1. Traditional-style: if the templates be the problem, let the
> templates be the solution:
> ============================================================
> class storage_exception { ... };
> class database_exception { ... };
>
> template <class T> struct storage_traits
> {
> const int max_record_length = 1000;
> using service_exception = storage_exception;
> };
>
> template <> struct storage_traits<database_storage>
> {
> const int max_record_length = 2000;
> using service_exception = database_exception;
> };
>
> class file_storage
> {
> bool read_record(char record[1000]) throw(storage_exception);
> };
>
> class database_storage
> {
> bool read_record(char record[2000]) throw(database_exception);
> };
>
> template <class T>
> void validate(T & storage) throw(typename
> storage_traits<T>::service_exception)
> {
> char record[storage_traits<T>::max_record_length];
> while (storage.read_record(record)) ...
> }
> ============================================================
>
>
> This strikes me as a correct solution, but it smells very C++98ish.
> This is basically analogous to C++98's
> template<class T>
> typename iterator_traits<T>::reference whatever(T it) {
> return *it;
> }
>
> It was very hard to use that sort of thing. We ended up with two
> different solutions to that problem:
> (1) Full-blown type inference.
> template<class T>
> decltype(auto) whatever(T it) { return *it; }
> This is analogous to your second solution below (but I don't like your
> second solution; see below).
> (2) decltype. That is, we give the programmer a way to say "This
> expression? Give me its type." (Similarly we have a way to say "This
> expression? Give me its noexceptness.")
> template<class T>
> decltype(*declval<T&>()) whatever(T it) { return *it; }
>
> Traits classes don't scale well for generic code because they always
> force bondage and discipline on /somebody/ — either the
> generic-algorithm-writer or the concrete-model-writer. Either the
> concept author says "`it+1` must always yield a value of exactly the
> type `T`" and then the concrete-model-writer has to deal with that
> constraint, or else the concept author says "`it+1` can yield anything
> lol" and then the generic-algorithm-writer has to deal with the
> constraint of never being able to mention `it+1` or else having to
> repeatedly do things in terms of `typename
> iterator_traits<T>::self_plus_1_type`. It's just awkward either way.
>
> The history of C++ suggests that we should invent a way to define the
> primary template of storage_traits as
> template<class T>
> struct storage_traits {
> using ...storage_exceptions = EXCEPTIONS_THROWN_BY(
> declval<T&>().read_record(nullptr) );
> };
> so that the user doesn't need to specialize it at all anymore. (I'm
> ignoring `storage_traits::max_record_length` because I think it's a
> distraction from the general case; in the general case we can't assume
> that a traits class will have any other motivation to exist.)
>
> So I agree that your first solution is simple and "in the spirit of
> C++", but I don't think it is practical, and I think users will not
> like it.
>
> 2. New-style: let the compiler deduce (the "auto" keyword already
> means "let compiler determine the type"!)
> ============================================================
> class file_storage
> {
> bool read_record(char record[1000]) throw(storage_exception);
> }
>
> class database_storage
> {
> bool read_record(char record[2000]) throw(database_exception);
> }
>
> template <class T>
> void validate(T & storage) throw(auto) // the compiler "knows"
> which "checked" exceptions are not handled
> {
> while (storage.read_record(record)) ... // the compiler "knows"
> what is "checked" for "T::readRecord()"
> }
> ============================================================
>
> `throw(auto)` smells like `noexcept(auto)` to me, which the Committee
> has repeatedly rejected. It syntactically appears in the signature
> (interface) of the function, but its actual meaning depends on the
> specific implementation of the function body.
> "Isn't that true of C++14 `auto` return types as well? And isn't it
> true of `constexpr` as well?" Yes, certainly. The Committee isn't
> consistent in its positions. :P But `auto` return types are awesome
> and super useful, so maybe they get more leeway. Even `noexcept(auto)`
> doesn't rise to the same level of usefulness as return-type-`auto`.
>
> Also, the user might well ask: "Okay, throw(auto) is awesome... but
> can't throw(auto) just be the default everywhere? Why do I have to
> explicitly /say/ that unhandled exceptions escape? That should be
> obvious!" Which of course defeats your whole point. But will the user
> care? They just want to write simple code. :)
>
> So, I think your second solution is reasonably usable (modulo the
> preceding paragraph), but I cannot foresee the Committee pursuing it
> at all. `throw(auto)` looks like `noexcept(auto)` and is Dead On
> Arrival for the same reasons.
>
> –Arthur

Received on 2020-06-22 17:16:02