Is this code supposed to throw or not ?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Gianni Mariani

    Is this code supposed to throw or not ?


    This is one of those, hugh ? moments.

    So, GCC behaves just like I would kinda expect it to but it looks VERY
    strange.

    It's one of those things that could cause silent strife if you included
    files in the wrong order.


    class X;

    extern X a;
    extern X b;

    // class X is incomplete ... right ?

    inline bool IsEqual( X & i, X & j )
    {
    // using operator & () on an incomplete class ... OK
    // OK we know what this is supposed to do ... right ?
    return (&i) == (&j);
    }

    class X
    {
    public:
    // BUT WAIT
    // Place a definition for operator & in the class.
    // - basically throw allways.
    X * operator & () { throw "Yikes"; };
    };

    inline bool IsEqualOverride ( X & i, X & j )
    {
    // using operator & () - should use the one defined in the cloass
    //
    return (&i) == (&j);
    }

    int main()
    {
    X x;

    // should this throw ?
    IsEqual( x, x ); // datapoint - GCC does not throw

    // this should throw ... right ?
    IsEqualOverride (( x, x ); // GCC throws here
    }



  • Rob Williscroft

    #2
    Re: Is this code supposed to throw or not ?

    Gianni Mariani wrote in news:bjuaql$op8 @dispatch.conce ntric.net:
    [color=blue]
    >
    > This is one of those, hugh ? moments.
    >
    > So, GCC behaves just like I would kinda expect it to but it looks VERY
    > strange.
    >
    > It's one of those things that could cause silent strife if you included
    > files in the wrong order.
    >[/color]
    <£0.02>
    Nope, its if you abuse the ability to declare incomplete types.
    </£0.02>
    [color=blue]
    >
    > class X;
    >
    > extern X a;
    > extern X b;
    >
    > // class X is incomplete ... right ?
    >
    > inline bool IsEqual( X & i, X & j )
    > {
    > // using operator & () on an incomplete class ... OK
    > // OK we know what this is supposed to do ... right ?
    > return (&i) == (&j);[/color]

    I suppose here the compiler should build an overload set for
    op & and A) select the right one or B) not find any and use
    the comiler generated op &.

    gcc does (B). We can't complain, Its done what we asked it too!
    [color=blue]
    > }
    >
    > class X
    > {
    > public:
    > // BUT WAIT
    > // Place a definition for operator & in the class.
    > // - basically throw allways.
    > X * operator & () { throw "Yikes"; };
    > };[/color]

    You could replace the above with:

    X * operator & ( X & ) { throw "Yikes"; };
    [color=blue]
    >
    > inline bool IsEqualOverride ( X & i, X & j )
    > {
    > // using operator & () - should use the one defined in the cloass
    > //[/color]

    Does the same again, this time uses (A).
    [color=blue]
    > return (&i) == (&j);
    > }
    >[/color]

    class X {}; // to go with the non-member op & above.
    [color=blue]
    > int main()
    > {
    > X x;
    >
    > // should this throw ?[/color]

    Absolutely *NOT* ( was that loud enough :) ), if it did the compiler
    would be doing something we didn't ask it to.
    [color=blue]
    > IsEqual( x, x ); // datapoint - GCC does not throw
    >
    > // this should throw ... right ?[/color]

    Once we remove the extra '(', it should, again its what we asked for.
    [color=blue]
    > IsEqualOverride (( x, x ); // GCC throws here
    > }
    >[/color]

    Note that the include order problem goes away if we 1) include
    a complete declaration for X with member op &, or 2) provide a
    declaration ( and optionaly a definition ) for a non-member op &.


    Just add a bit more confusion to the mix, if you put
    template < typename X > before the declaration of IsEqual() all
    3 (*) compilers I checked threw, even when X was complete before
    and op & was declared after, defered ADL I belive, though it
    could be that templates are fancy macro's :).

    (*) msvc 7.1 /Za, gcc 3.2 and bcc32.

    Rob.
    --

    Comment

    • Gianni Mariani

      #3
      Re: Is this code supposed to throw or not ?

      Rob Williscroft wrote:[color=blue]
      > Gianni Mariani wrote in news:bjuaql$op8 @dispatch.conce ntric.net:[/color]
      ....[color=blue]
      > <?0.02>
      > Nope, its if you abuse the ability to declare incomplete types.
      > </?0.02>[/color]

      Incomplete types are a convenient way to separate a hairy implementation
      from the rest of the application. I've used this to separate a state
      machine and an application. In the state machine implementation, a
      state was allowed to be part of an expression. In the rest of the
      application it was used as an indicator of the current state and the
      only interesting operator was is equal. There were other incomplete
      types e.g properties that were used to communicate values between app
      and state machine. The expression system was big and potentially
      misleading for the application and separating state machine and
      application code forced application programmers to structure their code
      better.

      I overlooked the unary & operator implications. (Not that it was used
      in the state machine) - just that it's the only operator that may me
      used on an incomplete class - but may be overridden once the class is
      defined.

      It should be one of those big red flahing warnings when overloading
      unary operator &.

      [color=blue]
      >
      > Absolutely *NOT* ( was that loud enough :) ), if it did the compiler
      > would be doing something we didn't ask it to.[/color]

      But a template would/might not ... right ?
      [color=blue]
      >
      >[color=green]
      >> IsEqual( x, x ); // datapoint - GCC does not throw
      >>
      >> // this should throw ... right ?[/color]
      >
      >
      > Once we remove the extra '(', it should, again its what we asked for.
      >
      >[color=green]
      >> IsEqualOverride (( x, x ); // GCC throws here
      >>}
      >>[/color]
      >
      >
      > Note that the include order problem goes away if we 1) include
      > a complete declaration for X with member op &, or 2) provide a
      > declaration ( and optionaly a definition ) for a non-member op &.
      >[/color]

      I can think of few things in C++ where within a single scope, behaviour
      of a function may change depending on where it is defined.
      [color=blue]
      >
      > Just add a bit more confusion to the mix, if you put
      > template < typename X > before the declaration of IsEqual() all
      > 3 (*) compilers I checked threw, even when X was complete before
      > and op & was declared after, defered ADL I belive, though it
      > could be that templates are fancy macro's :).
      >[/color]

      This is one more example whereby even though we use a template depending
      on where it is first instantiated, it will either throw or not.

      template <typename T>
      inline bool Tequal( T & i, T & j )
      {
      return (&i) == (&j);
      }

      class X;

      bool func( X & i, X & j )
      {
      return Tequal( i, j );
      }

      extern X a;
      extern X b;

      inline bool IsEqual( X & i, X & j )
      {
      return (&i) == (&j);
      }

      class X
      {
      public:
      X * operator & () { throw "Yikes"; };

      };

      inline bool IsEqualOverride ( X & i, X & j )
      {
      return (&i) == (&j);
      }


      int main()
      {
      X x;

      func( x, x );
      Tequal( x, x );

      // IsEqual( x, x );

      // IsEqualOverride ( x, x );
      }

      I'm starting to think this is a language defect.



      Comment

      • Rob Williscroft

        #4
        Re: Is this code supposed to throw or not ?

        Gianni Mariani wrote in news:bjvkrl$oop @dispatch.conce ntric.net:
        [color=blue]
        > Rob Williscroft wrote:[color=green]
        >> Gianni Mariani wrote in news:bjuaql$op8 @dispatch.conce ntric.net:[/color]
        > ...[color=green]
        >> <?0.02>
        >> Nope, its if you abuse the ability to declare incomplete types.
        >> </?0.02>[/color]
        >
        > Incomplete types are a convenient way to separate a hairy implementation
        > from the rest of the application. I've used this to separate a state
        > machine and an application. In the state machine implementation, a
        > state was allowed to be part of an expression. In the rest of the
        > application it was used as an indicator of the current state and the
        > only interesting operator was is equal. There were other incomplete
        > types e.g properties that were used to communicate values between app
        > and state machine. The expression system was big and potentially
        > misleading for the application and separating state machine and
        > application code forced application programmers to structure their code
        > better.
        >
        > I overlooked the unary & operator implications. (Not that it was used
        > in the state machine) - just that it's the only operator that may me
        > used on an incomplete class - but may be overridden once the class is
        > defined.
        >
        > It should be one of those big red flahing warnings when overloading
        > unary operator &.
        >
        >[/color]

        Well It really applies to all operator's and overloadable function's
        that take references or pointers.
        [color=blue][color=green]
        >>
        >> Absolutely *NOT* ( was that loud enough :) ), if it did the compiler
        >> would be doing something we didn't ask it to.[/color]
        >
        > But a template would/might not ... right ?[/color]

        A template should use ADL (koenig lookup) at the point of instantiation
        (as I understand it), so It should. My smart-ass remark about templates
        being macro's was aluding to the fact many compilers will get this
        right, even though they arn't doing the right thing.
        [color=blue]
        >[/color]

        [snip]
        [color=blue][color=green]
        >>
        >> Note that the include order problem goes away if we 1) include
        >> a complete declaration for X with member op &, or 2) provide a
        >> declaration ( and optionaly a definition ) for a non-member op &.
        >>[/color]
        >
        > I can think of few things in C++ where within a single scope, behaviour
        > of a function may change depending on where it is defined.[/color]

        Overload resolution changes it all the time.

        int f( double );
        int example() { return f( 0 ); }
        int f( int ); // move this up a line and the programme changes.
        [color=blue]
        >[color=green]
        >>
        >> Just add a bit more confusion to the mix, if you put
        >> template < typename X > before the declaration of IsEqual() all
        >> 3 (*) compilers I checked threw, even when X was complete before
        >> and op & was declared after, defered ADL I belive, though it
        >> could be that templates are fancy macro's :).
        >>[/color]
        >
        > This is one more example whereby even though we use a template depending
        > on where it is first instantiated, it will either throw or not.
        >[/color]

        [color=blue]
        > template <typename T>
        > inline bool Tequal( T & i, T & j )
        > {
        > return (&i) == (&j);
        > }
        >
        > class X;
        >
        > bool func( X & i, X & j )
        > {
        > return Tequal( i, j );
        > }
        >
        > extern X a;
        > extern X b;
        >
        > inline bool IsEqual( X & i, X & j )
        > {
        > return (&i) == (&j);
        > }
        >
        > class X
        > {
        > public:
        > X * operator & () { throw "Yikes"; };
        >
        > };
        >
        > inline bool IsEqualOverride ( X & i, X & j )
        > {
        > return (&i) == (&j);
        > }[/color]

        [snip - main()]
        [color=blue]
        >
        > I'm starting to think this is a language defect.
        >[/color]

        I changed main() thus:

        #define Try( x ) { \
        std::cout << #x "\t"; \
        try {{x} std::cout << "OK"; } catch (char const *s) { std::cout << s; } \
        std::cout << std::endl; }\

        int main()
        {
        X x;
        Try( func( x, x ); )
        Try( Tequal( x, x ); )
        Try( IsEqual( x, x ); )
        Try( IsEqualOverride ( x, x ); )
        }

        msvc 7.1 /Za:
        func( x, x ); Yikes
        Tequal( x, x ); Yikes
        IsEqual( x, x ); OK
        IsEqualOverride ( x, x ); Yikes

        gcc 3.2 (g++):
        func( x, x ); OK
        Tequal( x, x ); OK
        IsEqual( x, x ); OK
        IsEqualOverride ( x, x ); Yikes

        bcc32:
        func( x, x ); OK
        Tequal( x, x ); Yikes
        IsEqual( x, x ); Yikes
        IsEqualOverride ( x, x ); Yikes


        My reading would be that results should be OK, Yikes, OK, Yikes.
        But this is impossible, as it means having 2 version's of Tequal<X>.

        I think you're right about there being a defect in here. The question
        is - what's the defect and what's the fix:

        1) Have all overload resolution done by the linker - all output Yikes.
        this hasn't got any legs, it just wouldn't be C++ anymore!
        Breaks mode code than you can shake a stick at.

        2) Stop ADL-at-point-of-instantiation adding to the overload set.
        Output as gcc. But it breaks a whole bunch of existing template
        code, some in namesace std!

        3) Stop ADL-at-point-of-instantiation adding to the overload set if
        and only if there is one or more (possibly compiler generated)
        overloads available. Output as gcc again. Breaks less code than
        (2), possibly some in namespace std.

        I'd actually favour (2) as specialization does IMO a much cleaner
        job than ADL-at-point-of-instantiation, particularly if we get
        partial function specialization (*) as well.

        (*) This is being considerd I belive.

        Rob.
        --

        Comment

        Working...