Cyclic includes / forward definitions always solvable?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • John Doe

    Cyclic includes / forward definitions always solvable?

    Hi all,
    Regarding those cyclic dependencies of classes (like cases in which
    class A refers to class B and class B to class A): can they ALL be
    resolved by forward defining classes, and by implementing the actual
    functions outside the class definition, OR there are STILL cases which
    cannot be solved in this way and could require rewriting your code /
    rewriting your class hierarchy?
  • Leor Zolman

    #2
    Re: Cyclic includes / forward definitions always solvable?

    On Mon, 01 Mar 2004 13:14:30 -0600, John Doe <johndoe@nowher e.com> wrote:
    [color=blue]
    >Hi all,
    >Regarding those cyclic dependencies of classes (like cases in which
    >class A refers to class B and class B to class A): can they ALL be
    >resolved by forward defining classes, and by implementing the actual
    >functions outside the class definition, OR there are STILL cases which
    >cannot be solved in this way and could require rewriting your code /
    >rewriting your class hierarchy?[/color]

    Resolving cyclic dependencies has nothing to do with where you put the
    actual function implementations (including in the case of inline functions,
    since they can be defined "ex-situ"); it does have to do with whether or
    not the compiler has seen a "complete definition" of the class at the point
    where that information is required. Typically, the information of interest
    is the storage requirement for instances of that class.

    Note that a class definition would be considered complete even if it
    includes /pointers to incomplete types/.

    As far as I know, it all boils down to this: you cannot write any
    declaration or statement that requires the compiler to know how big a class
    is before you've provided a complete definition of that class.
    -leor

    Leor Zolman
    BD Software
    leor@bdsoft.com
    www.bdsoft.com -- On-Site Training in C/C++, Java, Perl & Unix
    C++ users: Download BD Software's free STL Error Message
    Decryptor at www.bdsoft.com/tools/stlfilt.html

    Comment

    • Ivan Vecerina

      #3
      Re: Cyclic includes / forward definitions always solvable?

      "John Doe" <johndoe@nowher e.com> wrote in message
      news:c2022p$o1u $1@news.vanderb ilt.edu...[color=blue]
      > Regarding those cyclic dependencies of classes (like cases in which
      > class A refers to class B and class B to class A): can they ALL be
      > resolved by forward defining classes, and by implementing the actual
      > functions outside the class definition, OR there are STILL cases which
      > cannot be solved in this way and could require rewriting your code /
      > rewriting your class hierarchy?[/color]

      A workaround is always possible, but there are still some
      tough cases that require an alteration of the design.


      For example, how would you use the following:

      struct A {
      std::list<B> myBs;
      };

      struct B {
      std::list<A> myAs;
      };

      For the next C++ standard, I believe that it is being
      considered to explicitly allow the the instantiation
      of some standard containers (e.g. std::vector)
      with incomplete types.
      But there is no such guarantee currently.



      Another classic problem is: how do you define a function f(void)
      that returns a pointer to an f(void) ?

      A workaround is to use an intermediate structure:
      struct FP {
      FP (*fp)(void);
      };

      FP f(void)
      {
      FP ans = {f};
      return ans;
      }

      This idiom is actually useful in some implementations
      of a state machine.



      Regards,
      Ivan
      --
      http://ivan.vecerina.com/contact/?subject=NG_POST <- e-mail contact form


      Comment

      • Jorge Rivera

        #4
        Re: Cyclic includes / forward definitions always solvable?

        Ivan Vecerina wrote:
        [color=blue]
        > "John Doe" <johndoe@nowher e.com> wrote in message
        > news:c2022p$o1u $1@news.vanderb ilt.edu...
        >[color=green]
        >>Regarding those cyclic dependencies of classes (like cases in which
        >>class A refers to class B and class B to class A): can they ALL be
        >>resolved by forward defining classes, and by implementing the actual
        >>functions outside the class definition, OR there are STILL cases which
        >>cannot be solved in this way and could require rewriting your code /
        >>rewriting your class hierarchy?[/color]
        >
        >
        > A workaround is always possible, but there are still some
        > tough cases that require an alteration of the design.
        >
        >
        > For example, how would you use the following:
        >
        > struct A {
        > std::list<B> myBs;
        > };
        >
        > struct B {
        > std::list<A> myAs;
        > };
        >[/color]

        I don't particularly understand the usefulness of this example.

        A is a strcuture that contains a list of B's, which in turns contains a
        list of A's, which contains a list of B's, which conains a list of
        A's....., you get the picture.

        I think it IS a good thing that compilers don't allow such horrible
        things...

        Comment

        • Andrey Tarasevich

          #5
          Re: Cyclic includes / forward definitions always solvable?

          Jorge Rivera wrote:[color=blue][color=green]
          >>
          >> For example, how would you use the following:
          >>
          >> struct A {
          >> std::list<B> myBs;
          >> };
          >>
          >> struct B {
          >> std::list<A> myAs;
          >> };
          >>[/color]
          >
          > I don't particularly understand the usefulness of this example.
          >
          > A is a strcuture that contains a list of B's, which in turns contains a
          > list of A's, which contains a list of B's, which conains a list of
          > A's....., you get the picture.[/color]

          I don't. In this case the list can be empty, which means that this
          recursion is not necessarily infinite.

          What you are saying is essentially the same as saying that functional
          recursion is impossible because it never ends.
          [color=blue]
          > I think it IS a good thing that compilers don't allow such horrible
          > things...[/color]

          I don't see anything horrible with it. It think you are mistaking this
          situation with other situation, which employs direct aggregation

          struct A { B b; };
          struct B { A a; };
          // Impossible and makes no sense

          This is not the same as the previous example with 'std::list's, since
          'std::list's implement indirect aggregation.

          --
          Best regards,
          Andrey Tarasevich

          Comment

          • EventHelix.com

            #6
            Re: Cyclic includes / forward definitions always solvable?

            The following article describes techniques for resolving such includes:



            Sandeep
            --
            Sequence diagram based systems engineering and architecture design tool. Built in support for alternative scenarios and multi-tier architectures.

            EventStudio 2.0 - System Architecture Design CASE Tool

            Comment

            • John Doe

              #7
              Re: Cyclic includes / forward definitions always solvable?

              > A workaround is always possible, but there are still some[color=blue]
              > tough cases that require an alteration of the design.
              >
              >
              > For example, how would you use the following:
              >
              > struct A {
              > std::list<B> myBs;
              > };
              >
              > struct B {
              > std::list<A> myAs;
              > };[/color]

              This compiles just fine in VC++7.1 ...

              [color=blue]
              > For the next C++ standard, I believe that it is being
              > considered to explicitly allow the the instantiation
              > of some standard containers (e.g. std::vector)
              > with incomplete types.
              > But there is no such guarantee currently.[/color]

              You mean that they are doing particular cases in the compiler like Java
              is doing for Strings? That would be really bad. I hate that: a language
              should be just a language and should treat all data types equally.

              But anyway I don't think it's the reason for which it compiles. I tried
              with my own template instead of std::string and it compiled on the same,
              because the std::string basically just uses pointers to the classes, so
              it just requires a forward definition of such classes in order to
              compile correctly, right?



              [color=blue]
              > Another classic problem is: how do you define a function f(void)
              > that returns a pointer to an f(void) ?
              >
              > A workaround is to use an intermediate structure:
              > struct FP {
              > FP (*fp)(void);
              > };
              >
              > FP f(void)
              > {
              > FP ans = {f};
              > return ans;
              > }[/color]

              This was GREAT code, unfortunately it does not compile.

              I guess it is because in the implementation of f you are putting a
              pointer to a global function, while the definition of FP requires a
              __thiscall function pointer (a function member of a class, in which
              "this" is passed secretely). I'm NOT sure though.

              Is there any cast which can make the above code compile? I tried but I
              wasn't able to do it.

              Thanks

              Comment

              • Ivan Vecerina

                #8
                Re: Cyclic includes / forward definitions always solvable?

                "John Doe" <johndoe@nowher e.com> wrote in message
                news:c258ov$ilg $1@news.vanderb ilt.edu...[color=blue][color=green]
                > > A workaround is always possible, but there are still some
                > > tough cases that require an alteration of the design.
                > >
                > >
                > > For example, how would you use the following:
                > >
                > > struct A {
                > > std::list<B> myBs;
                > > };
                > >
                > > struct B {
                > > std::list<A> myAs;
                > > };[/color]
                >
                > This compiles just fine in VC++7.1 ...[/color]

                But is not currently *guaranteed* to work ( => not legal C++ ).
                Container implementations would be allowed, for example,
                to store a root element within the class itself,
                or may otherwise use the sizeof() of the provided
                parameter to select a data member or specialization,
                or whatever...

                So the code is not (formally) portable, even though
                it might work with all your compilers in practice.
                [color=blue][color=green]
                > > For the next C++ standard, I believe that it is being
                > > considered to explicitly allow the the instantiation
                > > of some standard containers (e.g. std::vector)
                > > with incomplete types.
                > > But there is no such guarantee currently.[/color]
                >
                > You mean that they are doing particular cases in the compiler like Java
                > is doing for Strings? That would be really bad. I hate that: a language
                > should be just a language and should treat all data types equally.[/color]

                I did not mean that this would only apply to std::vector.
                The fact is: the code above works well with several implementations .
                But the standard allows standard library implementations to break it
                (e.g. it could be needed to allow some optimizations on some platforms).
                So declaring the above code above legal requires a cost/benefit analysis,
                and maybe it cannot apply to all containers (e.g. std::string,
                which often stores a small buffer as a data member, would not work).
                [color=blue]
                > But anyway I don't think it's the reason for which it compiles. I tried
                > with my own template instead of std::string and it compiled on the same,
                > because the std::string basically just uses pointers to the classes, so
                > it just requires a forward definition of such classes in order to
                > compile correctly, right?[/color]
                Right - in your implementation.
                [color=blue][color=green]
                > > Another classic problem is: how do you define a function f(void)
                > > that returns a pointer to an f(void) ?
                > >
                > > A workaround is to use an intermediate structure:
                > > struct FP {
                > > FP (*fp)(void);
                > > };
                > >
                > > FP f(void)
                > > {
                > > FP ans = {f};
                > > return ans;
                > > }[/color]
                >
                > This was GREAT code, unfortunately it does not compile.[/color]
                It compiles as is on Comeau++, which is considered by many as being
                a reference in compliance (http://www.comeaucomputing.com/tryitout/).

                What is the exact error message you are getting?
                [color=blue]
                > I guess it is because in the implementation of f you are putting a
                > pointer to a global function, while the definition of FP requires a
                > __thiscall function pointer (a function member of a class, in which
                > "this" is passed secretely). I'm NOT sure though.[/color]

                The sample above does not involve any member functions, just
                a data member that is of type pointer-to-function.
                [color=blue]
                > Is there any cast which can make the above code compile?
                > I tried but I wasn't able to do it.[/color]

                Please post the exact code you tried and associated error message.
                The snippet is 'kosher' as far as I can tell (although it is not
                an example of clarity...).


                Cheers,
                Ivan
                --
                http://ivan.vecerina.com/contact/?subject=NG_POST <- e-mail contact form


                Comment

                • John Doe

                  #9
                  Re: Cyclic includes / forward definitions always solvable?

                  Ivan Vecerina wrote:[color=blue]
                  > "John Doe" <johndoe@nowher e.com> wrote in message
                  > news:c258ov$ilg $1@news.vanderb ilt.edu...
                  >[color=green][color=darkred]
                  >>>A workaround is always possible, but there are still some
                  >>>tough cases that require an alteration of the design.
                  >>>
                  >>>
                  >>>For example, how would you use the following:
                  >>>
                  >>>struct A {
                  >>> std::list<B> myBs;
                  >>>};
                  >>>
                  >>>struct B {
                  >>> std::list<A> myAs;
                  >>>};[/color]
                  >>
                  >>This compiles just fine in VC++7.1 ...[/color]
                  >
                  >
                  > But is not currently *guaranteed* to work ( => not legal C++ ).
                  > Container implementations would be allowed, for example,
                  > to store a root element within the class itself,
                  > or may otherwise use the sizeof() of the provided
                  > parameter to select a data member or specialization,
                  > or whatever...
                  >[/color]

                  ??
                  In that case it's VERY RIGHT that it cannot compile, because it would
                  allocate an infinite-size structure due to recursion. Of course this is
                  off the topic of my initial question: I of course never intended to
                  compile impossible data structures.


                  [color=blue][color=green][color=darkred]
                  >>>A workaround is to use an intermediate structure:
                  >>> struct FP {
                  >>> FP (*fp)(void);
                  >>> };
                  >>>
                  >>> FP f(void)
                  >>> {
                  >>> FP ans = {f};
                  >>> return ans;
                  >>> }[/color][/color]
                  >[color=green]
                  >>I guess it is because in the implementation of f you are putting a
                  >>pointer to a global function, while the definition of FP requires a
                  >>__thiscall function pointer (a function member of a class, in which
                  >>"this" is passed secretely). I'm NOT sure though.[/color]
                  >
                  >
                  > The sample above does not involve any member functions, just
                  > a data member that is of type pointer-to-function.[/color]

                  Really?
                  If you do

                  FP fp;
                  //....
                  *(fp.f)();

                  you think that the f function called would not get a this pointer?
                  I think it would!

                  [color=blue][color=green]
                  >>Is there any cast which can make the above code compile?
                  >>I tried but I wasn't able to do it.[/color]
                  >
                  >
                  > Please post the exact code you tried and associated error message.
                  > The snippet is 'kosher' as far as I can tell (although it is not
                  > an example of clarity...).[/color]


                  Exactly your code.
                  The error I get is:

                  error C2440: 'initializing' : cannot convert from 'FP (__cdecl *)(void)'
                  to 'FP (__cdecl *)(void)'
                  Incompatible calling conventions for UDT return value

                  pointing at the line "FP ans = {f};"

                  I have seen that error before and it usually means that it cannot
                  convert a thiscall to a non-thiscall. And I agree with the compiler (see
                  above)

                  Comment

                  • Andrey Tarasevich

                    #10
                    Re: Cyclic includes / forward definitions always solvable?

                    John Doe wrote:[color=blue][color=green][color=darkred]
                    >>>>A workaround is to use an intermediate structure:
                    >>>> struct FP {
                    >>>> FP (*fp)(void);
                    >>>> };
                    >>>>
                    >>>> FP f(void)
                    >>>> {
                    >>>> FP ans = {f};
                    >>>> return ans;
                    >>>> }[/color]
                    >>[color=darkred]
                    >>>I guess it is because in the implementation of f you are putting a
                    >>>pointer to a global function, while the definition of FP requires a
                    >>>__thiscall function pointer (a function member of a class, in which
                    >>>"this" is passed secretely). I'm NOT sure though.[/color]
                    >>
                    >> The sample above does not involve any member functions, just
                    >> a data member that is of type pointer-to-function.[/color]
                    >
                    > Really?
                    > If you do
                    >
                    > FP fp;
                    > //....
                    > *(fp.f)();[/color]

                    This will not compile. Should be

                    (*fp.fp)();

                    or simply

                    fp.fp();
                    [color=blue]
                    > you think that the f function called would not get a this pointer?
                    > I think it would![/color]

                    No, it wouldn't. 'FP::fp' is declared as a pointer to ordinary
                    (non-member) function. It has nothing to do with any 'this'
                    [color=blue][color=green]
                    >> Please post the exact code you tried and associated error message.
                    >> The snippet is 'kosher' as far as I can tell (although it is not
                    >> an example of clarity...).[/color]
                    >
                    > Exactly your code.
                    > The error I get is:
                    >
                    > error C2440: 'initializing' : cannot convert from 'FP (__cdecl *)(void)'
                    > to 'FP (__cdecl *)(void)'
                    > Incompatible calling conventions for UDT return value
                    >
                    > pointing at the line "FP ans = {f};"[/color]

                    Interesting. I don't get anything laike this even though I seem to be
                    using the same compiler as you.

                    Seeing that two lines of code that you posted above already contain some
                    errors, I wouldn't be surprised if it turned out that you tested one
                    piece of code and thought about another.

                    --
                    Best regards,
                    Andrey Tarasevich

                    Comment

                    Working...