Exception Specifications

Hi,

I once tried to implement exception specifications, but after talking it
over with Doug, I reverted my changes. The matter is weird, because
while there is some type checking for exception specs, the spec is not
actually part of the canonical type - for example, two function pointers
with different exception specs do not cause different template
instantiations.

There are two ways to implement this. First, make exception specs part
of the canonical type, and handle the cases where it shouldn't be, or
don't make them part of the canonical type, and handle the cases where
there is a difference. The former feels more difficult, more work, and
most importantly, more likely to be wrong. Also, the mistakes you make
in the former would lead to reject-valid or silent miscompilation bugs,
whereas the mistakes you make in the latter would only lead to
accept-invalid bugs.

But where to store the exception spec? Do I store it in the
non-canonical type? I don't think I can store it only in the function
decl, since function pointers can have exception specs, and they don't
have a function decl.

Also, there is the question of efficient processing of the specs. I have
some vague ideas about that involving canonicalization of the spec, but
I was wondering if anyone had any experience with that kind of thing.

Sebastian

Hi Sebastian,

I once tried to implement exception specifications, but after talking it
over with Doug, I reverted my changes. The matter is weird, because
while there is some type checking for exception specs, the spec is not
actually part of the canonical type - for example, two function pointers
with different exception specs do not cause different template
instantiations.

Right. Note that we always perform template instantiation on canonical types, so if exception specs are *not* part of the canonical type, then we'll just ignore them in the type system.

There are two ways to implement this. First, make exception specs part
of the canonical type, and handle the cases where it shouldn't be, or
don't make them part of the canonical type, and handle the cases where
there is a difference. The former feels more difficult, more work, and
most importantly, more likely to be wrong. Also, the mistakes you make
in the former would lead to reject-valid or silent miscompilation bugs,
whereas the mistakes you make in the latter would only lead to
accept-invalid bugs.

The latter definitely seems like the right way to go. Basically, we say that exception specifications are like "sugar" in the type system, and in a few places we'll check for that sugar.

But where to store the exception spec? Do I store it in the
non-canonical type? I don't think I can store it only in the function
decl, since function pointers can have exception specs, and they don't
have a function decl.

I think it makes sense to put the exception specifications in the non-canonical type. We could invent a new type for this (say, FunctionProtoWithExceptionSpecType), which adds the exception specifications on top of an existing FunctionProtoType. Since we keep track of non-canonical types already, we'll keep the exception specifications around when we need them... and it should be relatively easy to insert the semantic checking that looks for FunctionProtoWithExceptionSpecType nodes in the few cases where we do semantic checking.

Also, there is the question of efficient processing of the specs. I have
some vague ideas about that involving canonicalization of the spec, but
I was wondering if anyone had any experience with that kind of thing.

I'm quite sure I don't understand the issues, here.

  - Doug

Douglas Gregor wrote:

Hi Sebastian,

Also, there is the question of efficient processing of the specs. I have
some vague ideas about that involving canonicalization of the spec, but
I was wondering if anyone had any experience with that kind of thing.

I'm quite sure I don't understand the issues, here.

    - Doug

Consider:

struct A {};
struct B : A {};
struct C : A {};
struct D : B {};

struct X {};
struct Y : X {};

void f() throw(Y, D, int, C);
void (*p)() throw(A, A, B, int, X);
p = &f;

The assignment here is valid. But I don't want the check to be quadratic
in the number of declarations, which a naive approach would be. So I
think I need to create a canonical form. In a canonical form, all
redundant declarations are thrown out, so the specs become (Y, int, C)
and (A, int, X). But that doesn't really reduce the problem.
Ideal would be some total ordering of types that allows me to cut off
the search if I know I can't find anything. In this ordering, non-class
types come before class types, and a class type comes after all its base
class types. In the example above, we could end up with (int, Y, C) or
(int, C, Y) for f and (int, A, X) or (int, X, A) for p.
However, that is still not sufficient! I'm still quadratic in the number
of class types. I'm sure there must be some ordering that allows me to
be linear, one that guarantees that I can walk the two specs, never
backtracing on either, and be guaranteed to find base types as I need
them. But I can't think of one.

That's why I asked if anyone knows some trick here.

I'm probably overthinking this. I'd be extremely surprised to find an
exception spec where quadratic performance matters. But ... it's still
quadratic. My soul rebels against writing such code. :wink:

Sebastian