Circular dependencies in Templates - better solution?

Discussion in 'C++' started by ro86, Mar 6, 2005.

  1. ro86

    ro86 Guest

    Hello everyone!

    I am a newbie to C++ (~1 Week experience) and I have a few months of
    experience with object-oriented languages (Objective-C). I am
    currently working just for fun on a particle system.

    All the particles are controlled by a "server". The server performs
    all kinds of operations on them (updating, drawing etc.). The
    particles (my "clients") on the other hand need to retrieve once a
    while some information from their server - they need to be hooked up
    to it. This works perfectly alright as long as I only have one
    server-class and one client-class. However if I want to add some
    subclasses and make everything more customizable I run into severe
    problems. The lack of run-time dynamism caused some problems on
    creating the server/client class structure.
    If I use a new client-class it will most likely need a new
    server-class. However its superclass already inherits a connection to
    the old server-class. I would need to re-implement all the member
    functions for the new server-class. In a pure dynamic language
    (Smalltalk/Objective-C) I could simply ignore the static type
    checking. To avoid compiler warnings I would simply cast the
    object-pointers when necessary (just for cosmetic reasons).
    C++ would not be C++ if it wouldn't deliver a solution for this
    dilemma - Templates. When I need a new client class, I just need to
    pass a new type parameter. Nice.
    And C++ wouldn't be C++ if no new problems would arise. I will
    demonstrate it.
    My server-class implementations:


    template <class GenericClient>
    class ServerClass
    {
    public:
    ServerClass()
    {
    clients = vector<GenericClient> (10, GenericClient(*this));
    }
    protected:
    vector<GenericClient> clients;
    int foo;
    };

    template <class GenericClient>
    class ServerSubclass : public ServerClass<GenericClient>
    {
    public:
    ServerSubclass() {}
    protected:
    int bar;
    };


    No problem until now. But now the client-class implementations:


    template <class GenericServer>
    class ClientClass
    {
    public:
    ClientClass() : server(NULL) {}
    ClientClass(GenericServer *aServer) { server = aServer; }
    protected:
    GenericServer *server;
    };

    template <class GenericServer>
    class ClientSubclass : public ClientClass<GenericServer>
    {
    public:
    ClientSubclass() {}
    ClientSubclass(GenericServer *aServer) :
    ClientClass<GenericServer>(aServer){}
    protected:
    int bla;
    };


    You can see that every client gets a connection to its server, when it
    is constructed. But now try to instantiate a server with a
    client-class.


    ServerSubclass<ClientSubclass<ServerSubclass<ClientSubclass...> > >


    We get an infinite circular dependency between the template classes.
    It is impossible to solve this circular dependency unless I have
    missed something fundamental about C++ templates.
    However there is a not so elegant way to do it with partially
    resorting to traditional OO constructions. All I need to do is to
    rewrite my client-subclass without templates.


    class ClientSubclass : public
    ClientClass<ServerSubclass<ClientSubclass> >
    {
    public:
    ClientSubclass() {}
    ClientSubclass(ServerSubclass<ClientSubclass> *aServer) :
    ClientClass<ServerSubclass<ClientSubclass> >(aServer) {}
    protected:
    int bla;
    };


    This looks confusing and it is indeed. But it is now possible to
    instantiate a server.


    ServerSubclass<ClientSubclass> myServer();


    However subclassing without a template is not only a way to solve the
    circular-dependency-problem but also to prevent any further
    subclassing. If I wanted to subclass my first-subclass with all its
    member functions I would need to convert my first subclass to a
    template class and then subclass it twice (once for the first and once
    for the second non-template subclass).

    Am I totally wrong? Have I totally missed something about OO design?
    Is there a more convenient way of doing this in C++ ?

    Any comments appreciated ;-)

    TIA
    Robert Potthast
     
    ro86, Mar 6, 2005
    #1
    1. Advertising

  2. ro86 wrote in news: in
    comp.lang.c++:

    >
    > ServerSubclass<ClientSubclass<ServerSubclass<ClientSubclass...> > >
    >
    >
    > We get an infinite circular dependency between the template classes.
    > It is impossible to solve this circular dependency unless I have
    > missed something fundamental about C++ templates.
    > However there is a not so elegant way to do it with partially
    > resorting to traditional OO constructions. All I need to do is to
    > rewrite my client-subclass without templates.
    >


    What you need is template template paramiters:

    #include <iostream>
    #include <ostream>
    #include <vector>


    template < template <typename> class GenericClient >
    class ServerClass
    {
    public:
    ServerClass() :
    clients(
    std::vector< GenericClient< ServerClass > > (
    10, GenericClient< ServerClass >(this)
    )
    )
    {
    }

    void print()
    {
    std::size_t i, len = clients.size();
    for ( i =0; i < len; ++i )
    {
    clients.print();
    }
    }

    protected:
    std::vector< GenericClient<ServerClass> > clients;
    int foo;
    };

    template < template <typename> class GenericClient >
    class ServerSubclass : public ServerClass<GenericClient>
    {
    public:
    ServerSubclass() {}
    protected:
    int bar;
    };


    template <typename GenericServer>
    class ClientClass
    {
    public:
    ClientClass() : server(NULL) {}
    ClientClass(GenericServer *aServer) { server = aServer; }
    virtual void print() {}
    protected:
    GenericServer *server;
    };


    template <typename GenericServer>
    class ClientSubclass : public ClientClass< GenericServer >
    {
    public:
    ClientSubclass() {}
    ClientSubclass(GenericServer *aServer) :
    ClientClass< GenericServer >(aServer), bla( 0 )
    {
    }
    ClientSubclass( ClientSubclass const &rhs ) : bla( ++rhs.bla )
    {
    }
    virtual void print() { std::cout << bla << '\n'; }
    protected:
    mutable int bla;
    };

    ServerSubclass< ClientSubclass > server;

    int main()
    {
    server.print();
    }

    HTH.

    Rob.
    --
    http://www.victim-prime.dsl.pipex.com/
     
    Rob Williscroft, Mar 6, 2005
    #2
    1. Advertising

  3. ro86

    Guest

    Rob Williscroft wrote:
    > template < template <typename> class GenericClient >
    > class ServerClass
    > {
    > //...
    > }


    Ok, this one is not easy.
    The server-class template assumes that you pass a class which is again
    a template as parameter. Right?

    > std::vector< GenericClient< ServerClass > > (
    > 10, GenericClient< ServerClass >(this)


    I think this is the tricky part. The compiler knows that the
    GenericClient awaits another class as parameter. If we would write this
    outside of our template we would have a circular (or recursive)
    dependency. But now we can pass the template class for our client as
    parameter and thus avoid the circular dependency.

    > HTH.

    Yes, it did ;)
    Thanks!
     
    , Mar 6, 2005
    #3
  4. wrote in news: in
    comp.lang.c++:

    > Rob Williscroft wrote:
    >> template < template <typename> class GenericClient >
    >> class ServerClass
    >> {
    >> //...
    >> }

    >
    > Ok, this one is not easy.
    > The server-class template assumes that you pass a class which is again
    > a template as parameter. Right?


    The paramiter is template-template paramiter and it must
    be passed a class-template that takes one type paramiter.

    >
    >> std::vector< GenericClient< ServerClass > > (
    >> 10, GenericClient< ServerClass >(this)

    >
    > I think this is the tricky part. The compiler knows that the
    > GenericClient awaits another class as parameter. If we would write this
    > outside of our template we would have a circular (or recursive)
    > dependency. But now we can pass the template class for our client as
    > parameter and thus avoid the circular dependency.
    >


    Well the bit you quoted is inside a constructor, templates and there
    members are only instantiated when they're needed. Before it
    instantiates the constructor it will instantiate the class to do that
    it needs to instantiate the data member:

    std::vector< GenericClient<ServerClass> > clients;

    to do that it may need to instantiate:

    GenericClient<ServerClass>

    Fortunatly this can be done without knowing anything about
    ServerClass as neither:

    ClientSubclass< ServerClass > or
    ClientSubclass< ServerSubClass >

    require knowing anything about ServerClass or ServerSubClass
    as they only use a pointers to a ServerClass.

    The terminoligy used to refere to this is "ClientSubclass can
    be instantiated with an incomplete type", ServerSubClass (and
    ServerClass) being the incomplete (because they're currently
    being instatiated) types.

    Also I noticed that the code I posted may not do precisley
    what you wanted, as the ServerSubClass contains a std::vector
    of ClientSubClass< ServerClass > where as IIUC you wanted
    a vector of ClientSubClass< ServerSubClass >.

    Here's the fixed code, it has an extra paramiter to ServerClass
    to feed in the sub-class type and a static_cast to cast from the
    base type to the derived type:

    #include <iostream>
    #include <ostream>
    #include <vector>


    template < template <typename> class GenericClient, typename Server >
    class ServerClass
    {
    public:
    ServerClass() :
    clients(
    std::vector< GenericClient< Server > > (
    10, GenericClient< Server >(static_cast< Server *>( this ) )
    )
    )
    {
    }

    void print()
    {
    std::size_t i, len = clients.size();
    for ( i =0; i < len; ++i )
    {
    clients.print();
    }
    }

    protected:
    std::vector< GenericClient< Server > > clients;
    int foo;
    };


    template < template <typename> class GenericClient >
    class ServerSubclass :
    public ServerClass<GenericClient, ServerSubclass< GenericClient > >
    {
    public:
    ServerSubclass() {}
    protected:
    int bar;
    };


    template <typename GenericServer>
    class ClientClass
    {
    public:
    ClientClass() : server(NULL) {}
    ClientClass(GenericServer *aServer) { server = aServer; }
    virtual void print() {}
    protected:
    GenericServer *server;
    };


    template <typename GenericServer>
    class ClientSubclass : public ClientClass< GenericServer >
    {
    public:
    ClientSubclass() {}
    ClientSubclass(GenericServer *aServer) :
    ClientClass< GenericServer >(aServer), bla( 0 )
    {
    }
    ClientSubclass( ClientSubclass const &rhs ) : bla( ++rhs.bla )
    {
    }
    virtual void print() { std::cout << bla << '\n'; }
    protected:
    mutable int bla;
    };



    ServerSubclass< ClientSubclass > server;


    int main()
    {
    server.print();
    }

    Rob.
    --
    http://www.victim-prime.dsl.pipex.com/
     
    Rob Williscroft, Mar 6, 2005
    #4
  5. ro86

    Guest

    This looks like the final solution. The original implementation hooked
    the ClientSubclass to the ServerClass up. All I now need to do is to
    wrap my classes into a elegant typedef and I can go on.
    This has been very helpful. Problems like these are the only way to
    learn more about complex languages like C++. The concept of templates
    is fascinating because its very flexible but offers very high
    performance (A glimpse into the future of meta-programming?). Learning
    by doing =)
    Danke!
     
    , Mar 6, 2005
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Suzanne Vogel
    Replies:
    2
    Views:
    2,632
    Suzanne Vogel
    Jun 26, 2003
  2. crichmon
    Replies:
    3
    Views:
    779
    crichmon
    Jun 28, 2004
  3. Dylan
    Replies:
    7
    Views:
    586
    Dylan
    Jul 7, 2004
  4. ernesto basc?n pantoja

    Circular dependencies

    ernesto basc?n pantoja, Nov 29, 2004, in forum: C++
    Replies:
    2
    Views:
    3,789
    Larry Brasfield
    Nov 29, 2004
  5. Kiuhnm
    Replies:
    16
    Views:
    757
    Jonathan Mcdougall
    Jan 3, 2005
Loading...

Share This Page