Topic: Multithreading and appropriate C++ syntax


Author: "Ken Hagan" <K.Hagan@thermoteknix.co.uk>
Date: 2000/08/03
Raw View
"Fraser Adams" <fraser.adams@virgin.net> wrote in message
news:Alvg5.160$Oy3.1095@news2-win.server.ntlworld.com...
>
> Threads (or rather the synchronisation and mutual exclusion mechanisms
> required by the paradigm) is a relatively difficult discipline to learn
> well and if a workable mechanism is included in another similar and
> commonly used language then it seems sensible to have C++ use analogous
> APIs unless a REALLY good alternative (that is to say intuitive and
> simple....) exists.

I don't think Java is a similar language. :-)

There used to be something called "Concurrent C". If memory serves me
correctly, it was an AT&T project at roughly the same time that C++ was.
(There was also a suggestion to create "Concurrent C++" by merging the
two projects.) Clearly, this was more like C than Java ever could be,
since it *was* just C, with a couple of extra keywords. Equally clearly,
it didn't catch on the way C++ did.

Dredging even deeper, I think it worked by using processes rather than
threads, since that's what Unix offered at the time. It was therefore
useful on architectures which weren't "symmetric multi-processors with
shared memory". (In fact, I think a second implementation ran over a
network cable.) I doubt if the Java model could be stretched this far.

Does anyone remember it?

If it died for a good reason, what lessons were learned about adding
threading to a language. If it simply died because it lost support at
some critical point in the project's lifetime, then perhaps it would
be worth raising it from the grave.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Michiel Salters <salters@lucent.com>
Date: 2000/08/04
Raw View
Ken Hagan wrote:

> There used to be something called "Concurrent C". If memory serves me
> correctly, it was an AT&T project at roughly the same time that C++ was.
> (There was also a suggestion to create "Concurrent C++" by merging the
> two projects.) Clearly, this was more like C than Java ever could be,
> since it *was* just C, with a couple of extra keywords. Equally clearly,
> it didn't catch on the way C++ did.

> Dredging even deeper, I think it worked by using processes rather than
> threads, since that's what Unix offered at the time. It was therefore
> useful on architectures which weren't "symmetric multi-processors with
> shared memory". (In fact, I think a second implementation ran over a
> network cable.) I doubt if the Java model could be stretched this far.

> Does anyone remember it?

Actually, for my M.Sc. thesis I looked at Concurrent C++, for which
an experimental compiler existed. Unfortunately, those developing it
seemed to have the philosophy that concurrency could be bolted on to C++,
so the compiler never got beyond the "Hello, world" phase. I.e. you could
try some features, other features never were implemented, and interaction
of the working features was also a matter of keeping fingers crossed. But
what probably really killed the project was that this occurred around
1995-1998, when C++ itself was rapidly evolving.

But the basic ideas looked very attractive.

> If it died for a good reason, what lessons were learned about adding
> threading to a language. If it simply died because it lost support at
> some critical point in the project's lifetime, then perhaps it would
> be worth raising it from the grave.

Well, the project would benefit from a stable C++ standard. OTOH, I
believe another erason why the project was stopped was because the developers
joined forces with another group, and started again from scratch.

PS. using multiple processes on a multiple-processor semi-UNIX machine
without threads, and without shared memory is exactly what I do nowadays.
And I can use plain C for that, with only OS messaging primitives.
Of course, it helps that the application is very parallel by its very nature.

Michiel Salters

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Fraser Adams" <fraser.adams@virgin.net>
Date: 2000/08/01
Raw View
Phil Edwards wrote in message <8lidd5$56q$1@dmapub.dma.org>...
>
>Fraser Adams <fraser.adams@virgin.net> wrote:
>+ That is to say an Object class comprising two attributes one of which is
a
>+ mutex, the other a condition monitor (both easily implemented using
>[snip summary of Java approach]
>+ approach, whilst it may offend C++ purists seems to be by far the most
>+ pragmatic approach.
>
>I don't see how this can work in C++.
>
>Because everything inherits from Object, all of these things can be easily
>done in Java (and all the other languages which have a single root object
>for /everything/ in the runtime program).  That can't be done in C++,
>because there's no way that we could impose such a requirement to the
>entire language.
>
>There are a couple of slightly-different alternatives.  For example, a
>project could create its own Object class (suitably enclosed in namespaces,
>of course), and everything /in that project/ would be required to descend
>from Object eventually.  This is common already, and seems to work well.
>But requiring that sort of change to the base language would break.
>

You misunderstood me I guess. My intention was just that: to implement these
mechanisms in a library. I don't really have a problem with this as the STL
is a library albeit one ratified by ANSI and included as part of the
language. It is not however core language and nor does the threads stuff
need to be.

I accept that this approach may not be to everyones taste, however I believe
that pragmatism should be used. Threads (or rather the synchronisation and
mutual exclusion mechanisms required by the paradigm) is a relatively
difficult discipline to learn well and if a workable mechanism is included
in another similar and commonly used language then it seems sensible to have
C++ use analogous APIs unless a REALLY good alternative (that is to say
intuitive and simple....) exists.

I've used the approach that I proposed in multithreaded server code (I also
wrote analogues of the Java Socket and ServerSocket classes). Firstly they
were pretty easy to use, secondly they made porting Java code that much
easier.

Regards, Fraser.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: pedwards@dmapub.dma.org (Phil Edwards)
Date: 2000/07/25
Raw View
Fraser Adams <fraser.adams@virgin.net> wrote:
+ That is to say an Object class comprising two attributes one of which is a
+ mutex, the other a condition monitor (both easily implemented using
[snip summary of Java approach]
+ approach, whilst it may offend C++ purists seems to be by far the most
+ pragmatic approach.

I don't see how this can work in C++.

Because everything inherits from Object, all of these things can be easily
done in Java (and all the other languages which have a single root object
for /everything/ in the runtime program).  That can't be done in C++,
because there's no way that we could impose such a requirement to the
entire language.

There are a couple of slightly-different alternatives.  For example, a
project could create its own Object class (suitably enclosed in namespaces,
of course), and everything /in that project/ would be required to descend
from Object eventually.  This is common already, and seems to work well.
But requiring that sort of change to the base language would break.

I always liked the approach that some parallel version of Pascal took:  in
addition to 'begin' and 'end' there were keywords 'cobegin' and 'coend'.
Everything between those two was deemed to be able to run in parallel.
In C++, of course, we'd have co{ and co}.  :-)


Phil

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Fraser Adams" <fraser.adams@virgin.net>
Date: 2000/07/23
Raw View
Ian Darling wrote in message <8k9anc$2er$1@newsg2.svr.pol.co.uk>...
>Well, I've been hearing a fair bit on these newsgroups (e.g, clcm, and
here)
>about adding multithreading support to C++.
>
>Well, as nobody seems to have come up with a syntax for it yet - I thought
>I'd throw in my two coppers.
>
<snipped the rest>

To be honest I'd go for an analogue of the Java mechanism (Since it already
has one that is perfectly workable and it would ease porting between the two
languages).

That is to say an Object class comprising two attributes one of which is a
mutex, the other a condition monitor (both easily implemented using
pthreads). This class would have a couple of methods lock and unlock (the
combination if which would lock and unlock the mutex and approximate Java
synchronized). In addition Objoct could contain wait, notify and notifyAll
methods using the condition monitor. A Thread class should be pretty easy to
write sitting over pthreads primitives and specialising from the Object
class and implementing a Runnable interface (or in C++ inheriting from a
Runnable abstract class containing a pure virtual run method). This
approach, whilst it may offend C++ purists seems to be by far the most
pragmatic approach.

Regards Fraser.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: qrczak@knm.org.pl (Marcin 'Qrczak' Kowalczyk)
Date: 2000/07/12
Raw View
Wed, 12 Jul 2000 02:57:50 CST, Christopher Eltschka <celtschk@physik.tu-muenchen.de> pisze:

> BTW, "critical" + volatile bool are enough to program a Mutex:
>
> class Mutex
> {
> public:
>   critical Mutex(bool to_be_locked): locked(to_be_locked) {}
>   critical void lock() { locked = true; }
>   critical void unlock() { locked = false; }
> private:
>   volatile bool locked;
> };

The above cannot work (unless it gets wrapped in something I cannot
imagine) because it treats true and false symmetrically.

I don't understand your borrow_ptr (I don't remember how auto_ptr
works). Never mind.

Mutex is MVar<unit> where unit is the trivial type with a single,
meaningless value.

http://research.microsoft.com/users/simonpj/Papers/concurrent-haskell.ps.gz
(in reality it changed in some details).

> > class Chan

> BTW, what does unget do?  the analog of pop_front (if read is back()
> + pop_back())?

It's like ungetc in stdio, i.e. push_back (if write is push_front
and read is pop_back).

Chan's implementation is actually quite complex: a pair of MVars,
each containing a private Stream, where Stream is an MVar containing
a value of type T and a Stream (when it's full of course).

> > class CVar

> >     void put (const T &); // May block.
> >     T get();              // May block.
> > };
>
> Seems to be an MVar with reduced interface, right?

No: put waits until there is a place, instead of being an error.
It's implemented using a pair of MVars: MVar<T> for producer->consumer
and MVar<unit> for back synchronization.

> > class QSem

> Can be implemented with atomic increment/atomic decrement.

It's not that simple or I don't understand something. How to arrange
blocking a set of threads and later unblocking one of them?

It's implemented as an MVar containing a non-negative int and a list
of MVar<unit> (the list is empty if the int is >0). These MVars are
empty, each has a thread waiting on it.

> > class QSemN

> What does this do?

Like QSem but you specify the number of repetitions of wait and signal,
where all must be done at the same time (s.wait(2) | s.wait(2) | s.signal(2)
in an appropriate initial state must wake one of threads, it cannot
give one point to each leaving both blocked). QSem is QSemN with numbers
fixed to 1.

--
 __("<  Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
 \__/            GCS/M d- s+:-- a23 C+++$ UL++>++++$ P+++ L++>++++$ E-
  ^^                W++ N+++ o? K? w(---) O? M- V? PS-- PE++ Y? PGP+ t
QRCZAK                5? X- R tv-- b+>++ DI D- G+ e>++++ h! r--%>++ y-


======================================= MODERATOR'S COMMENT:
 If this strays further from the C++ Standard, please take followups to a more appropriate forum.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Ian Darling" <ian@pure-virtual.co.uk>
Date: 2000/07/11
Raw View
Well, I've been hearing a fair bit on these newsgroups (e.g, clcm, and here)
about adding multithreading support to C++.

Well, as nobody seems to have come up with a syntax for it yet - I thought
I'd throw in my two coppers.

Things multithreaded programs need (in my youthful and high spirited
opinion):

(un)locking resources (ie, objects), and waiting for them to be released
critical sections or code
starting new threads
probably some sort of synchronisation between threads thing

and probably lots of stuff I've not thought of or am too new in industry to
really know about.

--

anyway - to start the ball rolling (and encourage the more experienced C++
users around to do some input too :) - how about the introduction of a
keyword "critical"?
To be used as such:

int main( void )
{
    critical
    {
        // do something uninterruptable by other threads in this process,
        // or even the whole system?
    }

    return 0;
}

the other use could be as part of a function declaration:

critical void crit_fn( void );

which would suggest to the compiler that this function could not be
interrupted until it had returned.

--

As for starting new threads - in my experience an API call of some sort is
done, which takes a function and starts a new thread, eg _beginthread() on
windows.  I'd suspect that this would end still up in a library, but be
templated so that arguments don't need to be passed around and cast as void
pointers.

template<class first> void start_thread( first );
template<class first, class second> void start_thread( first, second );

This would throw a newly defined exception - maybe std::bad_thread or
something, if the thread failed to start.

hence:

void fn( int *data );

// start a new thread in fn()

try
{
    start_thread<int *>( some_number );
}

catch( std::bad_thread )
{
    std::cout << "thread failed to start" << std::endl;
}

--

I've not really had a good opportunity to come up with reasonably neat ideas
for the rest - so I'll leave them as an exercise for the readers :)

--
Ian Darling


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: qrczak@knm.org.pl (Marcin 'Qrczak' Kowalczyk)
Date: 2000/07/11
Raw View
Tue, 11 Jul 2000 04:38:37 CST, Ian Darling <ian@pure-virtual.co.uk> pisze:

> how about the introduction of a keyword "critical"?

I don't think that blocking the whole process as the only possibility
is a good idea.

Concurrent Haskell has a simple but expressive locking/communication
primitive that would translate into C++ like this:

template <typename T>
class MVar
{
    // An MVar can be either empty or hold a value of type T.

  public:
    MVar();               // Start as empty.

    MVar (const T &);     // Start as full.

    T take();             // returns the contents if it was full,
                          // or waits until it becomes full otherwise.

    void put (const T &); // puts a value into an empty MVar. Calling
                          // put on an already full MVar is an error
                          // (an exception is raised).

    bool tryTake (T *);   // tryTake is a non-blocking version of take.
                          // If the MVar is full, then it stores the
                          // contents under the pointer if it's not null,
                          // empties the MVar and returns true. If the
                          // MVar is empty, it immediately returns false.

    T read();             // This is a combination of take and put;
                          // ie. it takes the value from the MVar,
                          // puts it back, and also returns it.
};

// MVar is the only locking/communication primitive. It is often used
// directly, for a value that needs synchronized access (this may not
// fit C++ well because C++ is not a functional language, and does not
// have garbage collection). Some higher level constructs built from
// MVars are provided as well:

template <typename T>
class Chan
{
    // A queue of values of type T.

  public:
    Chan();
    void write (const T &);
    T read();               // May block.
    Chan<T> dup();
    void unget (const T &);
};

template <typename T>
class CVar
{
    // A one-element channel.

  public:
    CVar();
    void put (const T &); // May block.
    T get();              // May block.
};

class QSem
{
  public:
    QSem (int);
    void wait();   // May block.
    void signal();
};

class QSemN
{
  public:
    QSemN (int);
    void wait (int);   // May block.
    void signal (int);
};

template <typename T>
class SampleVar
{
    // Similar to MVar, but writing to a full SampleVar overwrites
    // the current value.

  public:
    SampleVar();
    void empty();           // Empties the SampleVar.
    T take();               // May block.
    void write (const T &);
};

--
 __("<  Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
 \__/            GCS/M d- s+:-- a23 C+++$ UL++>++++$ P+++ L++>++++$ E-
  ^^                W++ N+++ o? K? w(---) O? M- V? PS-- PE++ Y? PGP+ t
QRCZAK                5? X- R tv-- b+>++ DI D- G+ e>++++ h! r--%>++ y-

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 2000/07/12
Raw View
Marcin 'Qrczak' Kowalczyk wrote:
>
> Tue, 11 Jul 2000 04:38:37 CST, Ian Darling <ian@pure-virtual.co.uk> pisze:
>
> > how about the introduction of a keyword "critical"?
>
> I don't think that blocking the whole process as the only possibility
> is a good idea.

He didn't say it should be the only possibility (he even did
say otherwise). It's just the easiest one to specify.

Another one that is easy to specify is a memory barrier:
Just add a standard function void memsync().
However, if it is defined that at beginning and end of
a critical section there should be a memory barrier,
this function may not even be needed - just write an
empty critical section (and rely on the preprocessor
to determine that two memory barriers without anything
in between are equivalent to one memory barrier).

BTW, "critical" + volatile bool are enough to program a Mutex:

class Mutex
{
public:
  critical Mutex(bool to_be_locked): locked(to_be_locked) {}
  critical void lock() { locked = true; }
  critical void unlock() { locked = false; }
private:
  volatile bool locked;
};

The private ensures that locked is not accessed from outside
the class, and the critical ensures that no concurrent accesses
to locked occur. Given that assignments to bool are quite fast,
it's unlikely that task switching would occur during execution
of the member function, and even if it would have occured, the
delay would be negligible. Therefore IMHO this is a valid
way to implement a Mutex (but then, I'm not a MT expert).

>
> Concurrent Haskell has a simple but expressive locking/communication
> primitive that would translate into C++ like this:
>
> template <typename T>
> class MVar
> {
>     // An MVar can be either empty or hold a value of type T.
>
>   public:
>     MVar();               // Start as empty.
>
>     MVar (const T &);     // Start as full.
>
>     T take();             // returns the contents if it was full,
>                           // or waits until it becomes full otherwise.
>
>     void put (const T &); // puts a value into an empty MVar. Calling
>                           // put on an already full MVar is an error
>                           // (an exception is raised).
>
>     bool tryTake (T *);   // tryTake is a non-blocking version of take.
>                           // If the MVar is full, then it stores the
>                           // contents under the pointer if it's not null,
>                           // empties the MVar and returns true. If the
>                           // MVar is empty, it immediately returns false.
>
>     T read();             // This is a combination of take and put;
>                           // ie. it takes the value from the MVar,
>                           // puts it back, and also returns it.
> };

This could be done in a more C++ish way with a "borrow_ptr".
A borrow_ptr mostly works like an auto_ptr, except that
each borrow_ptr returns the pointer to the borrow_ptr it got
it from (actually, borrow_ptrs form a linked list, so the case
that the previous owning pointer has disappeared is handled
by simply giving the pointer back to its previous owning
pointer). Only if there's no previous owning pointer, the
object is released.

A borrow_ptr models a shared ressource which can only be
possessed by one at any time (think of a library book:
Everyone can borrow the book, but only one at a time).
What happens if someone accesses an object which was
borrowed by someone else can be defined differently.
The simplest possibility is to provide a "passive"
NULL pointer - that is, the borrow_ptr is null, and
remains so, until assigned again. A slightly different
possibility is to insert it to the borrow chain just
after the pointer it tried to borrow from. This way,
the pointer gets the object as soon as it would have been
returned to the borrow_ptr it tried to borrow it from
(it's like if in a library you reserve a book borrowed
by someone else: As soon as he returns it, you get it
- unless someone else has reserve the same book earlier).
The third possibility - and the only one unimplementable
without multithreading - would be that the pointer not
only adds itself to the borrow chain, but also blocks
until it actually gets the ressource.

The MVar seems to be a combination of the "nulling"
borrow_ptr and the blocking borrow_ptr, with the
difference that returning to the previous possessor
is not automated (that is, you can borrow something
and forget to give it back). The borrow_ptr would
give it back automatically (unless you explicitly
asked it not to do so). This certainly comes with
extra overhead (the borrow list), but you get extra
safety in return (plus the possibility to create the
"non-blocking reserving borrow_ptr" without any
additional overhead).
Another difference is that borrow_ptr could be assigned
(in which case the original object, if existant, would
be returned to it's previous owner, or destroyed, if
there's no previous owner).

The single-thread borrow_ptr can be implemented with
current standard C++; the MT borrow_ptr only needs
mutexes. If critical suffices to implement a mutex,
it also suffices to implement a borrow_ptr. And
therefore, it also suffices to implement your MVar.

>
> // MVar is the only locking/communication primitive. It is often used
> // directly, for a value that needs synchronized access (this may not
> // fit C++ well because C++ is not a functional language, and does not
> // have garbage collection). Some higher level constructs built from
> // MVars are provided as well:

If all locking/communication in MT can be implemented with
MVar as only primitive, all that can also be implemented
with critical as only support construct.

Then we only need to provide another low-level construct to
start a thread, and we have all the low-level functionality
we need.

>
> template <typename T>
> class Chan
> {
>     // A queue of values of type T.
>
>   public:
>     Chan();
>     void write (const T &);
>     T read();               // May block.
>     Chan<T> dup();
>     void unget (const T &);
> };

We already have a value-oriented queue in C++. If combined with
a classical Mutex, we get a Chan. BTW, what does unget do?
the analog of pop_front (if read is back() + pop_back())?

>
> template <typename T>
> class CVar
> {
>     // A one-element channel.
>
>   public:
>     CVar();
>     void put (const T &); // May block.
>     T get();              // May block.
> };

Seems to be an MVar with reduced interface, right?

>
> class QSem
> {
>   public:
>     QSem (int);
>     void wait();   // May block.
>     void signal();
> };

Can be implemented with atomic increment/atomic decrement.
With critical, atomic in/decrement would be specified as
critical { ++i; } resp. critical { --i; }

>
> class QSemN
> {
>   public:
>     QSemN (int);
>     void wait (int);   // May block.
>     void signal (int);
> };

What does this do?

>
> template <typename T>
> class SampleVar
> {
>     // Similar to MVar, but writing to a full SampleVar overwrites
>     // the current value.
>
>   public:
>     SampleVar();
>     void empty();           // Empties the SampleVar.
>     T take();               // May block.
>     void write (const T &);
> };

OK, so this is like a borrow_ptr, except for the automatic
return (you get the raw object - actually the value, and are
responsible to return it yourself).

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: meixner@rbg.informatik.tu-darmstadt.de (Matthias Meixner)
Date: 2000/07/12
Raw View
Ian Darling (ian@pure-virtual.co.uk) wrote:
) Well, I've been hearing a fair bit on these newsgroups (e.g, clcm, and here)
) about adding multithreading support to C++.
)
) Well, as nobody seems to have come up with a syntax for it yet - I thought
) I'd throw in my two coppers.

[...]

) the other use could be as part of a function declaration:
)
) critical void crit_fn( void );
)
) which would suggest to the compiler that this function could not be
) interrupted until it had returned.

Probably we should wait until the research has come to a result before
integrating it into a standard, since there is still quite some research
going on in this field.

http://www.engr.csufresno.edu/Personal/CSci/Students/Grad/Rajeshwari_SusaiMicheal/ia.html

gives an introduction of the problems arising from the combination of
multithreading and object-oriented languages.

- Matthias Meixner

--
Matthias Meixner                   meixner@rbg.informatik.tu-darmstadt.de
Technische Universit   t Darmstadt
Rechnerbetriebsgruppe                          Telefon (+49) 6151 16 6670
Wilhelminenstra   e 7, D-64283 Darmstadt, Germany    Fax (+49) 6151 16 4701















---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]