How does dereference operator overloading really work?

Discussion in 'C++' started by Joe Seigh, Sep 21, 2003.

  1. Joe Seigh

    Joe Seigh Guest

    Is there a good write on this. The textbooks I have fluff over
    on this? Specifically, I trying to dereference with 2 levels
    of type conversion not 1, i.e.

    X<T> -> z => Y<T> -> z => T* -> z
    and
    *X<T> => *Y<T> => *T


    The Y<T> conversion has to be done as an expression temp. It cannot be done
    inside a method body for X.

    I think I sort of had it working for the -> operator but the same
    technique doesn't work for the * operator. The * appears to getting
    consumed.

    Joe Seigh
    Joe Seigh, Sep 21, 2003
    #1
    1. Advertising

  2. Joe Seigh wrote:
    > Is there a good write on this. The textbooks I have fluff over
    > on this? Specifically, I trying to dereference with 2 levels
    > of type conversion not 1, i.e.
    >
    > X<T> -> z => Y<T> -> z => T* -> z
    > and
    > *X<T> => *Y<T> => *T
    >
    >
    > The Y<T> conversion has to be done as an expression temp. It cannot be done
    > inside a method body for X.
    >
    > I think I sort of had it working for the -> operator but the same
    > technique doesn't work for the * operator. The * appears to getting
    > consumed.


    I don't really get what you're trying to do. Maybe posting a chunk o
    code that mostly compiles and show us what you'd like to do ?
    Gianni Mariani, Sep 21, 2003
    #2
    1. Advertising

  3. Joe Seigh wrote:

    > Is there a good write on this. The textbooks I have fluff over
    > on this?


    Uh... Did somebody swap the '?' and '.' keys on your keyboard?

    > Specifically, I trying to dereference with 2 levels
    > of type conversion not 1, i.e.
    >
    > X<T> -> z => Y<T> -> z => T* -> z
    > and
    > *X<T> => *Y<T> => *T
    >
    >
    > The Y<T> conversion has to be done as an expression temp. It cannot be done
    > inside a method body for X.
    >
    > I think I sort of had it working for the -> operator but the same
    > technique doesn't work for the * operator. The * appears to getting
    > consumed.


    I'm sorry, I have absolutely no idea what you are asking.

    -Kevin
    --
    My email address is valid, but changes periodically.
    To contact me please use the address from a recent posting.
    Kevin Goodsell, Sep 21, 2003
    #3
  4. Joe Seigh

    Joe Seigh Guest

    Gianni Mariani wrote:
    >
    > Joe Seigh wrote:
    > > Is there a good write on this. The textbooks I have fluff over
    > > on this? Specifically, I trying to dereference with 2 levels
    > > of type conversion not 1, i.e.
    > >
    > > X<T> -> z => Y<T> -> z => T* -> z
    > > and
    > > *X<T> => *Y<T> => *T
    > >
    > >
    > > The Y<T> conversion has to be done as an expression temp. It cannot be done
    > > inside a method body for X.
    > >
    > > I think I sort of had it working for the -> operator but the same
    > > technique doesn't work for the * operator. The * appears to getting
    > > consumed.

    >
    > I don't really get what you're trying to do. Maybe posting a chunk o
    > code that mostly compiles and show us what you'd like to do ?


    T * X<T>::eek:perator ->();

    gives one level of conversion. I don't want that. Something like


    Y<T> X<T>::eek:perator ->();
    T * Y<T>::eek:perator ->();


    I need a Y<T> temp generated. Strictly speaking, I would think no
    overloading of -> should be needed. Type conversion should be sufficient
    if I have a conversion path from X<T> to T*.

    Joe Seigh
    Joe Seigh, Sep 21, 2003
    #4
  5. Joe Seigh wrote in news::

    > Is there a good write on this. The textbooks I have fluff over
    > on this? Specifically, I trying to dereference with 2 levels
    > of type conversion not 1, i.e.
    >
    > X<T> -> z => Y<T> -> z => T* -> z
    > and
    > *X<T> => *Y<T> => *T
    >
    >
    > The Y<T> conversion has to be done as an expression temp. It cannot
    > be done inside a method body for X.
    >
    > I think I sort of had it working for the -> operator but the same
    > technique doesn't work for the * operator. The * appears to getting
    > consumed.
    >


    We really need to know what you are trying to do, otherwise
    all you can expect is guess, If you havent got any code to show
    perhapse give an example of the code whant to write and what you
    want it to actually do.

    Anyway here's my punt:


    #include <iostream>
    #include <ostream>

    template < typename T > struct thing;


    template < typename T >
    struct ptr_holder
    {
    T *temp;

    T *operator -> () const { return temp; }

    ptr_holder( T *arg ) : temp( arg )
    {
    thing< T >::aquire( temp );
    }

    ~ptr_holder()
    {
    thing<T>::release( temp );
    }
    };

    template < typename T >
    struct ref_holder
    {
    T *temp;

    operator T &() const { return *temp; }

    ref_holder( T *arg ) : temp( arg )
    {
    thing< T >::aquire( temp );
    }

    ~ref_holder()
    {
    thing<T>::release( temp );
    }
    };


    template < typename T >
    struct thing
    {
    T *data;

    static void aquire( T * data )
    {
    std::cout << "aquire( " << (void *)data << " )\n";
    }
    static void release( T * data )
    {
    std::cout << "release( " << (void *)data << " )\n";
    }

    ptr_holder< T > operator -> () const
    {
    return ptr_holder< T >( data );
    }

    ref_holder< T > operator * () const
    {
    return ref_holder< T >( data );
    }

    thing( T & ref ) : data( &ref ) {}

    };



    void function( int &arg )
    {
    std::cout << "function( " << arg << " )\n";
    }

    struct X
    {
    int y;
    X( int zz ) : y( zz ) {}
    };

    void function( X & x )
    {
    std::cout << "function( X( " << x.y << " )& )\n";
    }

    int main()
    {
    int a = 1;
    thing< int > at( a );
    function ( *at );

    X x( *at );
    thing< X > xt( x );
    std::cout << "inline: " << xt->y << "\n";
    function( *xt );
    }


    Rob.
    --
    http://www.victim-prime.dsl.pipex.com/
    Rob Williscroft, Sep 21, 2003
    #5
  6. Joe Seigh wrote:
    >
    > T * X<T>::eek:perator ->();
    >
    > gives one level of conversion. I don't want that. Something like
    >
    >
    > Y<T> X<T>::eek:perator ->();
    > T * Y<T>::eek:perator ->();
    >
    >
    > I need a Y<T> temp generated.


    Why would you need that? I suppose you could do something like this:

    T* X<T>::eek:perator->()
    {
    return T<T>()->something;
    }

    But I can't think of any reason to need a temporary.

    > Strictly speaking, I would think no
    > overloading of -> should be needed. Type conversion should be sufficient
    > if I have a conversion path from X<T> to T*.


    I still don't quite understand. If you want conversion from X<T> to T*,
    just define operator T*().

    -Kevin
    --
    My email address is valid, but changes periodically.
    To contact me please use the address from a recent posting.
    Kevin Goodsell, Sep 21, 2003
    #6
  7. Joe Seigh

    Joe Seigh Guest

    Kevin Goodsell wrote:
    >
    > > I need a Y<T> temp generated.

    >
    > Why would you need that? I suppose you could do something like this:
    >
    > T* X<T>::eek:perator->()
    > {
    > return T<T>()->something;
    > }
    >
    > But I can't think of any reason to need a temporary.


    It's for a smart pointer. Temps are local. If I get a local temp
    copy of the smart pointer generated, I can guarantee that the reference
    count will not go to zero for the duration of the expression. I can't
    make that guarantee if T* is generated directly.

    >
    > > Strictly speaking, I would think no
    > > overloading of -> should be needed. Type conversion should be sufficient
    > > if I have a conversion path from X<T> to T*.

    >
    > I still don't quite understand. If you want conversion from X<T> to T*,
    > just define operator T*().


    That didn't appear to work. I had a ctor Y<T>::Y(X<T>) and a
    Y<T>::eek:perator T*() defined.

    Joe Seigh
    Joe Seigh, Sep 21, 2003
    #7
  8. Joe Seigh

    Joe Seigh Guest


    >
    > Kevin Goodsell wrote:
    > >
    > > > I need a Y<T> temp generated.

    > >
    > > Why would you need that? I suppose you could do something like this:
    > >
    > > T* X<T>::eek:perator->()
    > > {
    > > return T<T>()->something;
    > > }
    > >
    > > But I can't think of any reason to need a temporary.

    >
    > It's for a smart pointer. Temps are local. If I get a local temp
    > copy of the smart pointer generated, I can guarantee that the reference
    > count will not go to zero for the duration of the expression. I can't
    > make that guarantee if T* is generated directly.
    >


    Though I suppose I could just not define -> for X:: and force using a Y ctor
    explicitly.

    struct thing {
    int z;
    };

    X<thing> p;

    p->z; // error, not allowed
    (Y<thing>(p))->z; // instead


    I did sort of get it working but when I did the * operator, I got
    *p doesn't work but **p does, so I'm not sure I know what is going
    on anymore. To be consistent, p->z would have to not work but
    p->->z would.

    Joe Seigh
    Joe Seigh, Sep 21, 2003
    #8
  9. Joe Seigh wrote:

    >
    > It's for a smart pointer. Temps are local. If I get a local temp
    > copy of the smart pointer generated, I can guarantee that the reference
    > count will not go to zero for the duration of the expression. I can't
    > make that guarantee if T* is generated directly.


    That makes it a bit more clear, but still not totally clear. Do you
    think you could post a very simple (but complete, so we can
    copy-paste-compile) program that demonstrates this problem?

    -Kevin
    --
    My email address is valid, but changes periodically.
    To contact me please use the address from a recent posting.
    Kevin Goodsell, Sep 21, 2003
    #9
  10. "Joe Seigh" <> wrote in message
    news:...
    > [...]
    > T * X<T>::eek:perator ->();
    >
    > gives one level of conversion. I don't want that. Something
    > like


    Are you sure you understand how smart pointers work?
    Typically, operator->() just returns the underlying pointer,
    and the compiler dereferences that pointer to get at the
    actual member. Given the operator above, there should
    be no problem with your later syntax:

    struct thing {
    int z;
    };

    X<thing> p;

    p->z; // error, not allowed

    The above should be fine.

    (Y<thing>(p))->z; // instead

    Not sure where this comes from.

    > [...]
    > I need a Y<T> temp generated.


    Why? Does the temp c'tor or d'tor have side effects? That
    would be very peculiar indeed.

    > Strictly speaking, I would think no overloading of -> should
    > be needed.
    > [...]


    If you are writing a smart pointer, overloading operator->
    is one of the most important things you need to do. But
    why write YASP (Yet Another Smart Pointer)? Do take
    a look at Loki::SmartPtr before reinventing the wheel for
    the Nth time.

    Dave
    David B. Held, Sep 21, 2003
    #10
  11. Joe Seigh

    Joe Seigh Guest

    "David B. Held" wrote:
    >


    >
    > If you are writing a smart pointer, overloading operator->
    > is one of the most important things you need to do. But
    > why write YASP (Yet Another Smart Pointer)? Do take
    > a look at Loki::SmartPtr before reinventing the wheel for
    > the Nth time.
    >


    No, this is an atomic thread-safe smart pointer. You're
    guaranteed to point to a valid object or null no matter
    what (the same guarantee that Java pointers have). No
    other C++ smart pointer can make that claim. They require
    you to own or have a lock on a smart pointer to access it
    or to dereference it.

    But this whole thing of C++ treating -> differently from
    * has me spooked and I have no explanation of what is
    going on. When C++ evaluate an expression, that expression
    is replaced by a value of the expression return type. Except
    when that expression is a -> expression. Then just the left
    hand part of the expression is replaced, the -> is left in,
    and the expression is re-evaluated.

    Joe Seigh
    Joe Seigh, Sep 21, 2003
    #11
  12. "Joe Seigh" <> wrote in message
    news:...
    > [...]
    > No, this is an atomic thread-safe smart pointer. You're
    > guaranteed to point to a valid object or null no matter
    > what (the same guarantee that Java pointers have). No
    > other C++ smart pointer can make that claim.


    LOL!!!

    > They require you to own or have a lock on a smart
    > pointer to access it or to dereference it.


    You really do need to read Modern C++ Design, and pay
    close attention to the chapter on smart pointers.

    > But this whole thing of C++ treating -> differently from
    > * has me spooked and I have no explanation of what is
    > going on.


    Simple. operator->() for smart pointers should always
    return an underlying pointer. operator*() should return
    a reference to the pointed-to object.

    > When C++ evaluate an expression, that expression
    > is replaced by a value of the expression return type.
    > Except when that expression is a -> expression. Then
    > just the left hand part of the expression is replaced, the
    > -> is left in, and the expression is re-evaluated.


    That's so that it's easy to write operator->(). Imagine if
    you had to compute struct member offsets yourself.

    Dave
    David B. Held, Sep 22, 2003
    #12
  13. Joe Seigh

    Joe Seigh Guest

    "David B. Held" wrote:
    >
    > "Joe Seigh" <> wrote in message
    > news:...
    > > [...]
    > > No, this is an atomic thread-safe smart pointer. You're
    > > guaranteed to point to a valid object or null no matter
    > > what (the same guarantee that Java pointers have). No
    > > other C++ smart pointer can make that claim.

    >
    > LOL!!!


    Atomic means that for the expression "p->a" where p is a
    smart pointer, a valid value (or null pointer exception)
    will be returned even if some other thread deletes or
    modifies p during the evaluation of that expression. What
    will not happen is a value returned from storage that
    has been reallocated as another object in the meantime.
    AFAIK all other smart pointers restrict what can happen
    to p during the evaluation of such an expression.

    The trick here (among other things) is to generate a local
    copy of the pointer as an expression temp during the
    evaluation of the expression. Since temps aren't dtored
    until after the the evaluation of the expression, the
    validity of the raw pointer value is guaranteed.

    The reason you don't see any of the other smart pointers
    using this trick is efficient thread-safe copying of the
    smart pointer is non-trivial.

    Joe Seigh
    Joe Seigh, Sep 22, 2003
    #13
  14. Joe Seigh

    Joe Seigh Guest

    Rob Williscroft wrote:

    > We really need to know what you are trying to do, otherwise
    > all you can expect is guess, If you havent got any code to show
    > perhapse give an example of the code whant to write and what you
    > want it to actually do.
    >


    Here's some code that illustrates the problem. It is not a smart pointer
    implementation. Just an illustration of a particular operator overloading
    issue.

    template<typename T> class X; // forward declare

    template<typename T> class Y {
    T * ptr;
    public:
    Y(X<T> & z) {
    ptr = z.ptr;
    }
    T * operator ->() { return ptr; }
    T & operator *() { return *ptr; }
    };

    template<typename T> class X {
    friend class Y<T>;
    T * ptr;
    public:
    X(T * p = 0) : ptr(p) {}
    Y<T> operator ->() { return Y<T>(*this); }
    Y<T> operator *() { return Y<T>(*this); }
    };

    class Item {
    public:
    Item(int x = 0) : z(x) {}
    int z;
    };

    int main(int argc, char *argv[]) {
    X<Item> p = new Item(99);
    Item k;
    int n;

    n = p->z;

    k = **p; // why 2 *'s and not 2 ->'s above?

    n = Y<Item>(p)->z; // this works

    n = (Y<Item>(p))->z; // this doesn't, vc++6.0 complains

    return 0;
    }

    Joe Seigh
    Joe Seigh, Sep 22, 2003
    #14
  15. Joe Seigh wrote in news::

    >
    >
    > Rob Williscroft wrote:
    >


    [snip]

    >
    > Here's some code that illustrates the problem. It is not a smart
    > pointer implementation. Just an illustration of a particular operator
    > overloading issue.
    >


    [snip]

    >
    > int main(int argc, char *argv[]) {
    > X<Item> p = new Item(99);
    > Item k;
    > int n;
    >
    > n = p->z;
    >
    > k = **p; // why 2 *'s and not 2 ->'s above?


    Because thats the way operator *() works, You can argue that its
    wrong (or not the most versitile way it could work) but changing
    it would break code.

    >
    > n = Y<Item>(p)->z; // this works
    >
    > n = (Y<Item>(p))->z; // this doesn't, vc++6.0 complains


    Get a better compiler VC 6.0 is quiet old now, VC 7.1 handles it fine
    BTW. Also this is clearly a bug so maybe its fixed by a service pack.

    >
    > return 0;
    > }
    >


    Unfortunatly we all have to work with the language as its defined,
    "warts and all", Its even worse for VC 6.0 users as it's a pre-standard
    compiler.

    You're example illustrated what you can't do with the language.

    Perhapse an example of what you wan't to achive would help here,
    i.e. What insterface you want to expose and some examples of the
    usage you expect.

    Rob.
    --
    http://www.victim-prime.dsl.pipex.com/
    Rob Williscroft, Sep 22, 2003
    #15
  16. "Joe Seigh" <> wrote in message
    news:...
    >
    > "David B. Held" wrote:
    > > [...]
    > > LOL!!!

    >
    > Atomic means that for the expression "p->a" where p
    > is a smart pointer, a valid value (or null pointer exception)
    > will be returned even if some other thread deletes or
    > modifies p during the evaluation of that expression.


    I wasn't laughing at the idea of a thread-safe pointer.
    I was laughing at the idea that you know every other
    type of smart pointer in existence. There is no doubt
    in my mind that there are hundreds of smart pointer
    types that you have never seen because they have not
    been released to the public. So to say that: "No
    other C++ smart pointer can make that claim" is just
    ridiculous.

    > What will not happen is a value returned from storage
    > that has been reallocated as another object in the
    > meantime.


    I can see why you want to do that, but ask yourself if
    this is the right level of locking. After all, most of the
    time that you want to access a resource in multiple
    threads, you also want to lock it for longer than the
    duration of one pointer access. So, for instance, if
    you do two successive pointer accesses into the same
    struct, it seems to me that you are locking the pointee
    twice, instead of once. So what are you paying for
    this "convenience"?

    > AFAIK all other smart pointers restrict what can
    > happen to p during the evaluation of such an
    > expression.


    And how many smart pointers do you know about?
    The other question is, if other smart pointers typically
    don't provide the functionality you wish to add, could
    there be a good reason for doing not so?

    > The trick here (among other things) is to generate a
    > local copy of the pointer as an expression temp during
    > the evaluation of the expression. Since temps aren't
    > dtored until after the the evaluation of the expression,
    > the validity of the raw pointer value is guaranteed.


    I don't see how that prevents another pointer to the
    same resource from deleting the resource out from
    under you. After all, the standard is thread-agnostic,
    so it makes no guarantees about program behaviour
    in the presence of multiple threads. In particular, I
    don't think it follows at all that a temp has some magical
    property which influences other threads. But I could
    just be misunderstanding your explanation.

    > The reason you don't see any of the other smart
    > pointers using this trick is efficient thread-safe copying
    > of the smart pointer is non-trivial.


    I think there's more reasons than that.

    Dave
    David B. Held, Sep 22, 2003
    #16
  17. "David B. Held" wrote:
    [...]
    > The other question is, if other smart pointers typically
    > don't provide the functionality you wish to add, could
    > there be a good reason for doing not so?


    His pointer provides STRONG thread-safety, not {more common} BASIC one.
    It's needed to sort of emulate {revised} Java volatile references with
    automatic garbage collection for things like:

    http://www.hpl.hp.com/personal/Hans_Boehm/gc/example.html

    or similar stuff using DCAS/whatever (avoiding blocking; "lock-free").

    regards,
    alexander.
    Alexander Terekhov, Sep 22, 2003
    #17
  18. Joe Seigh

    Joe Seigh Guest

    "David B. Held" wrote:
    >
    > "Joe Seigh" <> wrote in message
    > news:...
    > >
    > > "David B. Held" wrote:
    > > > [...]
    > > > LOL!!!

    > >
    > > Atomic means that for the expression "p->a" where p
    > > is a smart pointer, a valid value (or null pointer exception)
    > > will be returned even if some other thread deletes or
    > > modifies p during the evaluation of that expression.

    >
    > I wasn't laughing at the idea of a thread-safe pointer.
    > I was laughing at the idea that you know every other
    > type of smart pointer in existence. There is no doubt
    > in my mind that there are hundreds of smart pointer
    > types that you have never seen because they have not
    > been released to the public. So to say that: "No
    > other C++ smart pointer can make that claim" is just
    > ridiculous.


    Well, there's "Lock-Free Reference Counting" by Detlifs et al,
    but I don't know anyone using that since it requires something
    like the MC68020 with a DCAS instruction. There's weighted
    reference counting and something equivalent to it but my
    impression is that they weren't efficient enough for practical
    use. Reference counting is a form of GC, but you could use
    another form of GC such as RCU or Maged Michael's hazard
    pointers to make the refcount increment safe. Interestingly
    enough if you use Michael's double check logic in conjuction
    with LL/SC or ldwarx/stwcx instructions you can also increment
    the refcount safely.

    >
    > > What will not happen is a value returned from storage
    > > that has been reallocated as another object in the
    > > meantime.

    >
    > I can see why you want to do that, but ask yourself if
    > this is the right level of locking. After all, most of the
    > time that you want to access a resource in multiple
    > threads, you also want to lock it for longer than the
    > duration of one pointer access. So, for instance, if
    > you do two successive pointer accesses into the same
    > struct, it seems to me that you are locking the pointee
    > twice, instead of once. So what are you paying for
    > this "convenience"?


    There's two classes of this pointer. A global shared pointer
    class and a local non shared pointer class. The global
    is for mainly for use in the actual data structure and the
    local is for threads to access the struction. The local
    pointers have overhead equal to the non-atomic threadsafe
    pointers.

    It's not so much that lock-free pointers are all that fast
    by themselves, but they do let you implement other lock-free
    solutions that do blow conventional locking solutions out
    of the water.

    >
    > > AFAIK all other smart pointers restrict what can
    > > happen to p during the evaluation of such an
    > > expression.

    >
    > And how many smart pointers do you know about?
    > The other question is, if other smart pointers typically
    > don't provide the functionality you wish to add, could
    > there be a good reason for doing not so?


    Boost shared_ptr mainly. Either they didn't know how
    to without adding more overhead than they wanted or they
    decided it wasn't needed since they were requiring a
    higher level of locking anyway. The latter is a
    perfectly valid reason and seems to be the official
    reason given.

    >
    > > The trick here (among other things) is to generate a
    > > local copy of the pointer as an expression temp during
    > > the evaluation of the expression. Since temps aren't
    > > dtored until after the the evaluation of the expression,
    > > the validity of the raw pointer value is guaranteed.

    >
    > I don't see how that prevents another pointer to the
    > same resource from deleting the resource out from
    > under you. After all, the standard is thread-agnostic,
    > so it makes no guarantees about program behaviour
    > in the presence of multiple threads. In particular, I
    > don't think it follows at all that a temp has some magical
    > property which influences other threads. But I could
    > just be misunderstanding your explanation.


    The temp local copy of the pointer prevents the reference
    count from going to zero during the evaluation of the
    expression. If some other thread had deleted the global
    pointer during the evaluation of the expression then the
    dtor of the temp copy would actually delete the object
    after the expression was evaluated, not during or before.

    But I'm not trying sell anyone on this particular smart pointer.
    The OP is about some compiler or language behavior I'm seeing
    when trying to force the temp copy to be generated. It could
    be this whole double conversion technique is not really supported
    in C++ and I'd have do it in C instead.

    Joe Seigh
    Joe Seigh, Sep 22, 2003
    #18
  19. Joe Seigh

    tom_usenet Guest

    On Sun, 21 Sep 2003 18:11:52 GMT, Joe Seigh <>
    wrote:

    >Is there a good write on this. The textbooks I have fluff over
    >on this? Specifically, I trying to dereference with 2 levels
    >of type conversion not 1, i.e.
    >
    > X<T> -> z => Y<T> -> z => T* -> z
    >and
    > *X<T> => *Y<T> => *T
    >
    >
    >The Y<T> conversion has to be done as an expression temp. It cannot be done
    >inside a method body for X.
    >
    >I think I sort of had it working for the -> operator but the same
    >technique doesn't work for the * operator. The * appears to getting
    >consumed.


    operator-> is a bit odd. It's not that operator* gets consumed, but
    that operator-> is recursive.

    I think you need smart references, which don't exist yet. e.g.

    smart_reference<T> operator*();

    where the reference behaves almost exactly as if it were a T&, but you
    can do stuff in the destructor (such as unlock a mutex or decrement a
    reference count). Since you can't overload "operator." this isn't
    currently possible.

    Even without operator., you can still write an acceptable version of
    smart_reference by providing operator T&, operator=, etc. You just
    won't be able to do:

    (*p).doit();

    You could easily argue that users should write:

    p->doit();

    anyway, so you might not consider it a problem.

    Tom
    tom_usenet, Sep 22, 2003
    #19
    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. John Smith
    Replies:
    2
    Views:
    417
    Ivan Vecerina
    Oct 6, 2004
  2. PKH
    Replies:
    4
    Views:
    388
  3. TuxC0d3
    Replies:
    6
    Views:
    1,632
    John Carson
    Dec 6, 2005
  4. Replies:
    11
    Views:
    727
    James Kanze
    May 16, 2007
  5. hurcan solter
    Replies:
    3
    Views:
    725
    Cholo Lennon
    Aug 29, 2007
Loading...

Share This Page