C++ sucks for games

K

Kaz Kylheku

Svein Ove Aas said:
At this point I'd like to point out that, although complex numbers are part
of the standad CL library, if they weren't, it would be fairly easy to add
them.

Also, I'd like to point out that some things you can do with Lisp
complex numbers are difficult in C++.

Firstly, if you use CLOS methods for arithmetic operations, you can
support all of the combinations. C++ can only do this statically,
because dynamic dispatch (virtual functions) is done on what is
effectively the leftmost argument of a method (i.e. the object, or
``this'' pointer).

In Lisp, some block of code can receive two numbers whose type is not
statically apparent, and call an operation. The right operation is
selected based on both their types. This is called multiple dispatch:
the method selection takes into account the type of all objects---the
run-time type---somewhat analogously to how C++ overload resolution
does it over static types.

There are hacks to do this in C++ such as the Visitor Pattern.
Dynamically dispatch on the left argument to get to implementation i
of virtual function X, which then dynamically dispatches an
implementation of Y_i on the right argument. If you want three
arguments, the complexity explodes further; you then want to write a
Perl script to just generate the tedious C++ from a condensed spec.

Secondly, the Lisp numeric type system incorporates the idea that
value influences type. For instance, a complex number whose imaginary
part is zero is a real number. Or a rational number whose denominator
is one has integer type. The return value of an arithmetic computation
does not have some fixed declared type; the type is determined by the
value that is produced. If you divide 2 and 3, you get the rational
2/3. If you divide 4 by 2 using exactly the same function, you get
integer 2. Take the square root of -1 with (sqrt -1) and you get the
complex number #c(0 1). And so on.

Lastly, the two components of a complex number are not necessarily
real numbers. They can be fixed integers or bignums. This is largely
transparent.

For example, suppose you add these two complex numbers like this:

(+ #c(-1 -1) #c(1 1))

The result is 0. That is, to say, *integer* zero. Not a complex zero,
or real zero. The real and imaginary parts of these complexes are
integers, and they all nicely cancel out to produce a pure zero.

These types of robust properties of the Lisp numeric tower make it
useful for solving numeric problems, with clear, concise code in which
The Right Thing happens.
 
?

=?ISO-8859-1?Q?Andr=E9_Thieme?=

Computer said:
I am also pointing out the fact that if Lisp is supposed to be good for the
"newbie" then how come it looks WORSE off than every other programming
language that I have "gazed" upon?

If the person is a newbi, how can he/she know that not all programming
languages look like that? ;-)

Also: give it a try for three days. There will be no problem anymore.


André
 
?

=?ISO-8859-1?Q?Andr=E9_Thieme?=

JKop said:
A question:


Why do people still talk about C?


As far as I'm aware, C++ is and can do everything that C can do, but has
more features. Would anyone be able to inform me of any reason why
people still programme in C, or why they even talk about it?


-JKop

You might take a C++ compiler to get C code. In theory they should be
able to produce the same output.
Anyway, a program compiled with a C++ compiler which is not using any
features of C++ is still a C program. So if you don't use classes,
operator overloading, templates, exception handling etc.. then you are
programming in C. The fact that you use a C++ compiler to make machine
language out of your sources does not make your program any C++isher.

As soon you begin to use the more complex features of C++.. a fight will
start. You will run into problems where you need to think how you can
tell it (=what you want to achieve) the compiler. The problem itself is
history.. first you need to fight with the language to get things done,
then you can come back to the real problem itself.


André
 
J

Jerry Coffin

(e-mail address removed) (Kaz Kylheku) wrote in message
[ ... ]
What trouble with Lisp? What is your Lisp experience?

Lisp has a number of problems, particularly fragmentation. The
fragmentation _has_ reduced considerably since CL was invented, but
unfortunately CL is, in some ways, the worst possible dialect.

I can't speak for the person to whom you were posting, but my Lisp
experience includes having used it off and on for 20+ years, and
having written three (or four, depending on your viewpoint)
implementations during that time.

[ ... ]
Speaking of Forth, that
is the language you must be thinking of which requires some right to
left thinking.

While Forth (using RPN) obviously does, Lisp does as well, for at
least some situations, particularly mathematical ones, and for almost
exactly the same reason: in both cases, what starts as a mathematical
expression has to be transcribed into the language so the syntax
directly expresses the precedence of operations that are implied in
the original expression.

OTOH, none of the above really compares with APL for right-to-left
reading.

[ ... ]
if ...
for ...
while ...
switch ...
class ...

All prefix operators, like in Lisp.

Pardon my pointing out the obvious, but in C or C++, none of the above
is an operator at all.
Oh yes and there are lists all
over the place in C++ source. Lists of function and template
arguments, lists of declarations and statements, lists of base
classes, lists of base class initializers, lists of this lists of
that. They all have inconsistent syntax with each other! Semicolon
terminators here, comma separators there. Initial colon elsewhere. In
Lisp source code, anything that is a list has the same syntax as any
other list, whether it be a list of arguments, list of base classes,
list of variable forms in a LET or whatever.

Partly true, but not entirely so -- in particular, see below about
M-expressions.

[ ... ]
Lisp was deliberately designed so that the main connective is easy to
find without parsing. This isn't a coincidence; it makes it easier to
write code which analyzes and processes code. Such code is found in
Lisp macros, which are parts of a Lisp program that extend the
language with new syntax and semantics that other parts of a program
use.

When John McCarthy designed Lisp, the intent was that S-expressions
would be used for data. Programmers were intended to write code using
M-expressions, which have a syntax more like other languages,
including some inconsistencies, such as using a mixture of parentheses
and square brackets, as well as sometimes putting the first item in a
list outside the brackets and sometimes inside.

The original intent was that M-expressions would be translated to
S-expressions, and then those would be interpreted. As a first step,
an interpreter that accepted S-expressions was written. Before a
front-end, or even a syntax for the language it would accept, was
finished, the S-expression syntax had been accepted to the extent that
M-expressions never materialized.

You might (even well be right to) believe that Lisp is better off for
this, but claiming it was a deliberate design, at the very best,
distorts the facts beyond recognition.
 
K

Kelsey Bjarnason

[snips]

* Closures

Don't know about those, I'll check the thread - but are these particularly
significant, given that C++ (and presumably C, and presumably umpteen
other similar languages) don't support them? I mean, they can hardly be
the be-all and end-all of programming, right? Just how much of an
improvement _do_ they make over not having them?
* Macros (ability to add your own control structures, etc.)

Not sure what you mean here, but last I checked, C++ did support macros.

* More sophisticated OO system (multiple dispatch, etc.)

Great. C++'s model is already sufficiently complex to be annoying, and
you want a more complex model?

* Garbage collection.

Never seen this one defended as being actually useful for anything but
hand-holding of incompetent coders.

* More sophisticated exception handling system (allows restarts).

Not sure what "allows restarts" means; if I catch an exception, it's up to
me how to proceed: free some resources and try the operation again, for
example.
Better idioms for resource control (e.g. unwind-protect). * More
paradigms suppported: Imperative, OO, functional, logic (if you use a
library like Screamer).

Okay, this sounds reasonable. Not that I've ever needed imperative or
logic coding, but if one needs them and they can't be accomplished in C or
C++, then, sure.
 
B

Brian Downing

Not sure what "allows restarts" means; if I catch an exception, it's
up to me how to proceed: free some resources and try the operation
again, for example.

With one important difference - in C++ once you get in the handler the
stack has already been unwound. Once this happens, you've lost all your
state, and if there was a way to recover you now have to tediously
rebuild it.

The Common Lisp condition system does not require this - the exception
handler can be run in the dynamic environment of the exception, not the
handler.

More importantly, however, it provides a protocol to present multiple
ways of resolving the error and restarting the computation. These can
be selected either interactively or programatically. The restart is a
function that will usually perform some corrective procedure and then
transfer control to a point where computation can continue.

Of course, if you want "C++-style" exception behavior, where handlers
run after the stack unrolls, it's available.

-bcd
 
M

mikel

Kelsey said:
[snips]

* Closures


Don't know about those, I'll check the thread - but are these particularly
significant, given that C++ (and presumably C, and presumably umpteen
other similar languages) don't support them? I mean, they can hardly be
the be-all and end-all of programming, right? Just how much of an
improvement _do_ they make over not having them?

Closures are a terrific advantage.
Not sure what you mean here, but last I checked, C++ did support macros.

The previous poster is talking about Lisp macros; C and C++ support
nothing like them. C and C++ macros are entirely different animals.
Specifically, C and C++ macros are string substitutions at preprocessing
time; Lisp macros are syntax-tree-to-syntax-tree transformations
performed by a Lisp runtime, with all Lisp operators available to the
code that performs the transformation.
* More sophisticated OO system (multiple dispatch, etc.)

Great. C++'s model is already sufficiently complex to be annoying, and
you want a more complex model?

'Sophisticated' and 'complex' don't mean the same thing. CLOS is not
distinguished by a startlingly large number of features, but by the
great power CLOS provides for constructing object-oriented language
features.
* Garbage collection.

Never seen this one defended as being actually useful for anything but
hand-holding of incompetent coders.

People used to say the same thing about subroutines and recursive
function calls. Like those two once-controversial features, GC makes
programming much easier and less error-prone.
* More sophisticated exception handling system (allows restarts).

Not sure what "allows restarts" means

It refers to one of the features provided by the CL condition system.
Lisp systems are interactive. When something breaks, you enter an
interactive debugger with the incremental compiler built into it; you
can inspect the state of the runtime, redefine functions and data
structures interactively, and restart the computation from where it
broke. Restarts are user-definable options for resuming computation
after an exception occurs. They can be used either during development,
or to provide exception-handling features in a delivered app, or both.

This kind of exception-handling leads to an idiosyncratic form of
top-down and test-driven development, because you can start with the
toplevel function or with a test definition, and just run it. When it
hits an undefined function or data structure it will of course break,
but you can just define the missing features from the debugger and
continue, repeating that process until the toplevel function runs to
completion, or the test succeeds.
 
C

Computer Whizz

Alex Gian said:
If the syntax of "(+ 2/3 2/3 1/3)" confuses you, then I expect you will
have massive problems when you encounter C++'s templates, parameterized
types, and STL!! :) :) :)

But I expect those are pleasures that still lie in store for you!
For instance, cam you understand the following easily?
It's just a random snippet from yesterday's std C++ list.

template <typename E, typename T>
friend std::basic_ostream< E, T>& operator<<
( std::basic_ostream< E, T>& os, const A& obj)

If you can, but still experience difficulty with "(+ 2/3 2/3 1/3)", then I
expect you have a mind that works in a very interesting way....

Why do people always think it's the easiest, fraction part of the original
post I referred to?
Simply because I replied right at the end? Perhaps it's reading that Lisp,
meaning the middle to the last then the first - or some other peculiarity?
*dodges flames from last comment*

And I understand most of that C++ statement, as I haven't fully covered
Types yet... I also need to do some practice as I've only compiled a handful
of my own dodgy code.
 
C

Computer Whizz

Kaz Kylheku said:
What trouble with Lisp? What is your Lisp experience?

Very little experience, I have only seen what is in this newsgroup thread.

I do however correct myself and claim "my" trouble.
That is actually wrong; to properly understand a Lisp expression, you
must work from outside in. You first consider it to be a list, where
the first element denotes the identity of the operator. Otherwise you
can get tripped up. For instance, suppose you had:

(defclass a (foo b c) ..)

As Jerry has replied - it is indeed important to find the core in maths
problems (all/99%) of which I have see in here, while also some function
calls. You need to scan to see a rough idea, then re-scan to perhaps see the
parameters and then rescan again to see the function calls.

The above looks at a quick scan (without paying attention to defclass) as a
function foo being called, as you say below... But after moving leftward the
mind is corrected.
If you were looking from the inside out, you might think that there is
a function call (FOO B C). But in fact, this is a DEFCLASS form, where
class A is being defined, and its base classes (multiple inheritance)
are FOO, B and C. The DEFCLASS operator has complete control over the
interpretation of the rest of the form; you have to know its semantics
in order to understand that form.

You read the form left to right. The main connective is always
leftmost. Its constituents can be read from left to right, and their
connectives are also on the left and so forth. Speaking of Forth, that
is the language you must be thinking of which requires some right to
left thinking.

The C and C++ languages have plenty of prefix forms. Function calls:

f(g(x, y), z); // analogous to (f (g x y) z)

Now here it is much better for C/C++ IMO.
parameters are passed using the ()'s to group them together, and the
function outside. While in Lisp, the function and parameters are in exactly
the same place.
For me it's exactly like writing this post but removing all the comma's,
full stops etc and just leaving the words and spaces - the flow seems to
dissapear.
Please tell us it's for good.

Thanks!

And no, I do not back down on my own personal views until I am satisfied
with what I am given.
 
C

Computer Whizz

Kaz Kylheku said:
These are the technical, objective advantages of Lisp syntax:

Objective is right.
- precedence and associativity do not exist, and so there are no
ambiguities

- every well-formed syntax corresponds to a data structure. There is a
bidirectional conversion path between a data structure and syntax,
allowing Lisp programs to manipulate Lisp source code.

- a few simple rules can be applied to format Lisp syntax in a
readable way, even if it has very deep nesting. Lisp can be written as
a sideways tree (think of a Windows tree control, for instance):

a +--- b +-- e
| |
| +-- f
|
+--- c
|
+--- d --- e

Just add parentheses and keep basically the same shape:

(a (b e
f)
c
(d e))

That is horrible in my eyes, almost as horrible as the C++ mess you made
below.
From this you can tell right away that there is a main list headed by
the main connective A, with three other constituents. They all start
on separate lines and are at the same indentation level.

All I can tell if that there's a mess.
This formatting can be applied to very deep expressions to make sense
out of them.

You cannot nicely format deeply nested infix expressions, because the
connectives are sandwiched between the constituents; they do not
nicely pop out on one side for a sideways diagram.

In the prefix notation, the main connective comes first, which allows
you to put the other ones to the left and on separate lines, without
rearranging the lexical order of the tokens.

Given (+ a b) you can write that as:

(+ a
b)

But given A + B, you cannot write that as:

A
+
B

Why?

A +
B - is fine enough.
because the order has been permuted. Your compiler won't take it.
There isn't any nice order in which you can separate the connective
from the constituent expressions.

Sometimes I use this style in C++,

A +
B

So that say ((A || B) && C) && !(X || Y) becomes (oh boy, I can't even
see what the main connective is):

((A || B) && C) &&
!(X || Y)

That is good enough. You can see that The first and last needs to be true
etc.
I think that two liner is fine, breaks the code into two suitable parts.
then

((A ||
B) &&
C) &&
!(X ||
Y)

Blech, anomaly with the ! operator which is prefix. This quickly
becomes way too ugly and the main connective doesn't stick out; it's
still buried. The first && sticks out to the far right, looking more
prominent. Maybe:

((A ||
B) &&
C) &&
!(X ||
Y)

Now this is just a right mess... I need to parse the WHOLE thing into your
head and try to seperate it out.
now we at least have a sideways tree shape of sorts. But this is ugly
as sin. If we translate to Lisp we get:

(and (and (or a
b)
c)
(not (or x
y)))

Beautiful. I can mentally convert that to a combinational logic
circuit diagram flowing right to left.

Beauty is in the eye of the beholder indeed!
I can mentally convert that into a bit less of a mess.
But perhaps it's that I don't convert it into such a diagram but merely
understand what happens to lead into the next. I can see the Lisp is easier
for a diagram - but personally harder for me to understand.
We can selectively fold obvious pieces to one line:

(and (and (or a b)
c)
(not (or x y)))

(and (and (or a b) c)
(not (or x y)))

Alot better - just like the C++ example above.
(and (and (or a b) c) (not (or x y)))

These kinds of transformations are easy to do back and forth,
particularly with Lisp-aware editors.

Even in the completly flat version, I know right away that the main
opeation is an AND, without having to parse through the whole thing.

In my eyes the two liner C++ code looks better since the && is on the right,
in the middle of the two - where I naturally look when I want to know what
is happening...
The Lisp two-liner is actually nicer on the eye, it just looks cleaner. But
I can't be sure that isn't because of all the lower case.

As alot of things, it's subjective on your mindset - so I'm questioning if
I'm actually adding to this discussion, or am simply getting tired of this
whole thread as neither syntax will come out on top - since it's ALL
subjective.

Let the replies of "this is better because" continue...
 
C

Computer Whizz

André Thieme said:
If the person is a newbi, how can he/she know that not all programming
languages look like that? ;-)

Also: give it a try for three days. There will be no problem anymore.


André

I shall investigate it pretty soon, as I shall be getting alot more free
time... I shall then just pop in to your lisp newsgroup to post my
thoughts... Don't expect anything of me though - the mindset is boggling!
 
S

Svein Ove Aas

Computer said:
Why do people always think it's the easiest, fraction part of the original
post I referred to?
Simply because I replied right at the end? Perhaps it's reading that Lisp,
meaning the middle to the last then the first - or some other peculiarity?
*dodges flames from last comment*

And I understand most of that C++ statement, as I haven't fully covered
Types yet... I also need to do some practice as I've only compiled a
handful of my own dodgy code.

Well, he didn't *have* to write it that way; I find it overly complex
myself, no pun intended. The (complex) function dynamically constructs
complex numbers, but when it's done with constants, I'd prefer something
like this:

(+ #C(3 3) #C(3 -3) 3) => 9

It's still a bit confusing, but I challenge you to come up with anything
simpler in C++. More usefully, the compiler will constant-fold this down to
9 in the first place (function calls or no function calls), whereas the C++
compiler I tried it in notably didn't.
 
G

Gareth McCaughan

Gerry said:
I don't dispute it. Self-modifying code is one example. But it's an
example that suggests to me that Lisp programmers are generally more
interested in programming than in shipping software.

I don't understand how it can do that, since Lisp doesn't use
self-modifying code.

For what it's worth, I've seen no evidence that Lispers are
either more or less prone than C++ers to get caught up in the
details of programming rather than actually getting things
completed. I've certainly seen Lispers in that state. I've
seen C++ers in the same state, and at least one famous (and
rather good, in its way) C++ book seems to me to be a fine
example. I don't think this tells us anything about either
language. It tells us that people who do something that's
(at least sometimes) both difficult and interesting, like
writing software, will sometimes find they're more interested
in the process than in the product. It happens plenty outside
the world of software, too.
It's one of
several things that give me that impression. Another is the simple
enthusiasm of its advocates. There's nothing like an unfinished project
to generate enthusiasm for a programming language. You're doing the fun
stuff, not tidying up the loose ends.

So a language whose programmers are enthusiastic about it
is likely to be one that isn't used for getting real work
done? That rules out almost every language I've either
used or seen except, maybe, COBOL.
If I feel the need to learn and use Lisp I will do so. So far I don't
see any good reason to interest myself in it. If that's my loss, so be
it.

That's fine. No one, I hope, is trying to force you to do
anything you don't want to. Certainly not me.
 
A

Alex Gian

Computer said:
And I understand most of that C++ statement, as I haven't fully covered
Types yet... I also need to do some practice as I've only compiled a handful
of my own dodgy code.
Good luck with your C++, anyway.

When you get to the standard C++ lbraries (STL), you may find it helpful
to study the influence that Lisp had on them.

I know it helped me undestand them better...
 
K

Kaz Kylheku

Computer Whizz said:
Now here it is much better for C/C++ IMO.
parameters are passed using the ()'s to group them together, and the
function outside. While in Lisp, the function and parameters are in exactly
the same place.

How are they in the same place? The function is in the leftmost
position, and the arguments are not!

The function belongs with the arguments; enclosing it together with
them expresses this grouping.

Consider what happens when the prefix syntax where functions are
outside of the group is combined with infix operators:

x * f(x, y)

Now what is the grouping? Is it (x * f)(x y)? Or do you just assume
that the prefix operator always has a higher precedence than the
binary *, whatever it is? That doesn't quite work:

x + f(x, y); // additive expression, function call is tighter
foo::bar(x, y); // C++ scope resolution, foo goes with bar
obj.func(x, y); // member selection, obj goes with func.

But:

(obj.*pmemb)(x, y); // .* low precedence, parens needed!

Ooops.

If you move the function to the right of the parenthesis, these
ambiguities disappear.
For me it's exactly like writing this post but removing all the comma's,
full stops etc and just leaving the words and spaces - the flow seems to
dissapear.

The text is in fact mostly words and spaces. The, punctuation, is,
quite, minimal, compared, to, C++, Pascal, Etc. What a waste of
keystrokes; what do the commas accomplish? They disambiguate things
like x, ++y versus x++, y. One syntactic sdidiocy is patched over
with another. What a false economy; just so that you could have these
prefix operators and whatnot, you have to type extra punctuation even
when you are not using them!
 
R

Rahul Jain

Maahes said:
By bottom-up, I mean, your grouping all your lowest expression together and
then operating on them, and then operating on the result of that.

For instance (an example someone posted).

C:
for (int i=0; i<=10; i++) list.push(i);
list.reverse();


List:

(let ((lst 0))
(dotimes (i 11)
(push i lst))
(reverse lst))



In the C version, I am typing straight out of my head in left -> right
fashion and appending new work as I go along. In the Lisp one, it seems you
have to group the (push i lst), the (i 11), as they are the bottom level
actions. Then wrap that in a (dotimes a b) framework, then wrap that in a
let (a b) framework.

No, you type the code exactly as you read it: from left to right. You
could type the C code by doing list.push(i) first and then adding the
for loop and then doing the reverse, but I'm sure that won't make any
sense to you. In the same way, what you describe makes no sense to me.

Although, I would write that code in Lisp as:

(loop for i below 11 collect i)

;)

The main thing you fail to realize, due to the complexity of C syntax,
is that C code has just as much structure as Lisp code (more actually,
because the structure goes immediately to semantically significant
constructs), but that structure is obfuscated by the variety of
characters used to delimit various semantic constructs. In Lisp, we just
have one set of characters to delimit all constructs and the semantics
are determined by the operator at the beginning of a form.
I'm not even sure why there are 2 brackets around the
lst 0. This seems to make a mockery of Lisp having consistent syntax.

The bindings list of a LET form is just that, a list, so it needs to
have that extra level of nesting.
On top of all that, there are so many darn brackets there that I find that
it takes me a few seconds to match them up in my head to see which groups
are arguments to which functions, though I expect your quite used to the
indenting so it makes instant sense to you. I would imagine a far more
complex line, with a dozen brackets, would be a major brainstrain to
comprehend.

Your problem is that you're looking at the parens, not at the shape of
the code. Do you actually look for the semicolons at the end of every
statement in C or do you simply let your editor line up the code so that
all statements in the same block have their starting line indented to
the same level and any further lines (because of lots of chained
function calls or because of lots of parameters split up onto multiple
lines) indented further in?
 
H

Hartmann Schaffer

Jerry said:
...
When John McCarthy designed Lisp, the intent was that S-expressions
would be used for data. Programmers were intended to write code using
M-expressions, which have a syntax more like other languages,
including some inconsistencies, such as using a mixture of parentheses
and square brackets, as well as sometimes putting the first item in a
list outside the brackets and sometimes inside.

The original intent was that M-expressions would be translated to
S-expressions, and then those would be interpreted. As a first step,
an interpreter that accepted S-expressions was written. Before a
front-end, or even a syntax for the language it would accept, was
finished, the S-expression syntax had been accepted to the extent that
M-expressions never materialized.

iirc, one of the many texts on the history of lisp went into detail abut
that: the people working on and with this interpreter discovered
macros, used then heavily, and saw no way how to preserve their power
when switching to M-expressions

hs
 
R

Rahul Jain

Kelsey Bjarnason said:
This is not a language issue; this is a _tools_ issue. This is a question
of whether or not your _debugger_ allows you to do this sort of thing;
nothing in the C or C++ language specs prevents it.

You mean that there's a specified protocol for updating instances of a
class when the class's definition has changed in C++? Interesting.
 
A

Alex Gian

Maahes said:
I'm not even sure why there are 2 brackets around the lst 0.

(Should be (lst '()), anyway, as someone already commented.
This seems to make a mockery of Lisp having consistent syntax.

No, its perferctly consistent.
It's because you might want to initialise more than one variable for the
loop. So,

(let ((i 0)(j 100)) (...))

would coreespond to C++'s

for (int i=0, int j=100; <testcondition>; <loop-ops, e.g i++,j-->) {}
 
R

Rahul Jain

Computer Whizz said:
And no - the trouble with Lisp to begin with is trying to find the "core" ()
and work outwards from there - kind of backward to my natural way of
thinking... Like reading right-to-left.
The one's I've quoted are short.

Not even the compiler is supposed to read your code from the inner
parens and work out. The compiler must arrange for evaluation of the
code from left to right, as this is how the code is read (being based on
the English language).
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,206
Messages
2,571,069
Members
47,674
Latest member
scazeho

Latest Threads

Top