Off-Topic: Quake II Ported to HTML5

[…]

x = construct_object()
ObjectContainer.Add(x)

//End of block - ‘x’ is automatically… oops!

That’s why you have refcounting or similar. :wink:

That indeed has a bunch of issues, but it’s quite handy if you want
deterministic behavior and minimal “garbage collection” delays for optimal
memory footprint.

[…]

More useless typing. Even more useless typing if you want a pointer to
a pointer, or a pointer to an array, or an array of pointers, or a
pointer to an array of pointers, etc…

I’m sorry, but that’s extremely vague and sounds more like ranting than
anything useful. How is writing out information that makes a program
easier to read when you come back to it (or when someone else has
to maintain it) “useless”?

+1

(Not that I’m a Delphi fan specifically - although I have developed Delphi
applications, and I don’t think the language is all that horrible; just mildly
annoying, mostly because I’m so used to C and C++… :wink:

I’m really not a fan of nonsensically wordy syntax, but being able to clearly
express your intentions is very useful, both for trapping things at compile
time, and for making the code more human readable. Even some redundancy to
help catch typos can be warranted at times.

Why?

Because most of us spend much more time messing with existing code (fixing
bugs, adding features, reusing old code in new projects etc), than writing
brand new code. Some extra typing may cost a second or two per line, but if it
can save hours down the road, can you really afford to “save” those
seconds…?

[…]

(And it definitely makes reading good code easier. You can’t say that
about anything from the C family.)

That might, to some extent, be an effect of “us” C/C++ programmers taking
pride in writing “clever” code. ;-)On Thursday 08 April 2010, at 23.14.43, Mason Wheeler wrote:


//David Olofson - Developer, Artist, Open Source Advocate

.— Games, examples, libraries, scripting, sound, music, graphics —.
| http://olofson.net http://kobodeluxe.com http://audiality.org |
| http://eel.olofson.net http://zeespace.net http://reologica.se |
’---------------------------------------------------------------------’

[…]

(And it definitely makes reading good code easier. You can’t say that
about anything from the C family.)

That might, to some extent, be an effect of “us” C/C++ programmers taking
pride in writing “clever” code. :wink:

That is something that I really force myself to avoid. It is incredible what
some people
do manage to write.

It is cool as a way to impress your friends, but not if you have to maintain
the code,
whatever language it might be.On Thu, Apr 8, 2010 at 11:42 PM, David Olofson wrote:


Paulo

[…]

x = construct_object()
ObjectContainer.Add(x)

//End of block - ‘x’ is automatically… oops!

That’s why you have refcounting or similar. :wink:

That indeed has a bunch of issues, but it’s quite handy if you want
deterministic behavior and minimal “garbage collection” delays for optimal
memory footprint.

Yeah, I love reference counting. It doesn’t work all that well when you’re
working with objects that can hold references to each other, but when it is
usable it’s very useful.>----- Original Message ----

From: David Olofson
Subject: Re: [SDL] Off-Topic: Quake II Ported to HTML5
On Thursday 08 April 2010, at 23.14.43, Mason Wheeler <@Mason_Wheeler> wrote:

[…]

Yeah, I love reference counting. It doesn’t work all that well when you’re
working with objects that can hold references to each other, but when it is
usable it’s very useful.

The number one problem I have with it in EEL is “back pointers”; for example
GUI widgets referring back to their container widgets. As it is, I need to
explicitly Close() a GUI window or similar, where in a perfect world, it would
just go away along with the last reference, as other objects do.

Another problem is “managed” objects that aren’t visibly attached to anything
from the API POV. The “manager’s” references keep them around after the
application code releases them. EEL itself has similar issues internally with
modules and their functions. (Solved for now with some nasty “garbage
collection” specifically for modules…)

Not the Holy Grail, but weak references solves both of those problems - and
that pretty much covers it for me, so far. Actually working on that right now.
(Wanted my “SFont” implementation to cache fonts without causing a memory leak

  • and that was just one silly module level Close() call too many. :slight_smile:

I’ve considered having a specialised GC manage container objects specifically
(as only those can create cycles), but that’s tricky business in realtime
environments…!On Thursday 08 April 2010, at 23.52.38, Mason Wheeler wrote:


//David Olofson - Developer, Artist, Open Source Advocate

.— Games, examples, libraries, scripting, sound, music, graphics —.
| http://olofson.net http://kobodeluxe.com http://audiality.org |
| http://eel.olofson.net http://zeespace.net http://reologica.se |
’---------------------------------------------------------------------’

It’s useless extra typing, which is the main problem with the "classic"
statically typed languages that the dynamic languages have managed to
fix. Compare:

Python:

def f():
x = g()
h(x)

Pascal:

procedure f;
var x: Integer;
begin
x = g();
h(x);
end;

The Pascal version uses fully twice as many lines of code, and they’re
longer too.

Mmhmm. But there’s a good reason for it. In Python, you could do that,
and call g(), which returns a dictionary, and pass the result to h(),
which
expects a number. In Pascal, the compiler makes sure you don’t do that.

Do you see me advocating dynamic typing anywhere? Here is the
equivalent in D:

void f() {
auto x = f();
g(x);
}

It’s larger than the Python version, but much smaller than the Pascal
version. It’s also statically typed.

There’s only so much you can do to reduce typing. At some point it becomes
counter-productive. I would consider the number of syntactic elements
necessary to produce an effect: Pascal’s ‘begin’ is equivalent to C’s ‘{’.
I do certainly prefer symbols over words (and Python’s indentation bugs
me!) when the symbolic meaning is clear (as in this case), but code editors
can make keywords just as recognizable with syntax highlighting. Don’t
underestimate code completion either! Your typing is reduced but your
output and readability is maintained.

Also important is that you don’t get worked up over others’ opinions. They
are just that.

Jonny DOn Thu, Apr 8, 2010 at 1:44 PM, Rainer Deyke wrote:

The following is a very common idiom, perhaps the most
common resource management idiom:

x = alloc_resource()

free_resource(x)
// End of block.

It’s also a stupid idiom because it clutters the code. This following
is much better:

x = alloc_resource()

// End of block - ‘x’ is automatically freed.

Why shouldn’t this be automated? It’s an absolutely trivial task!
There is no way a language facility could mess this up!

x = construct_object()
ObjectContainer.Add(x)

//End of block - ‘x’ is automatically… oops!

Again, the compiler can and should detect this.

Nope. I just gave a trivial example that can break any compiler’s
ability to keep track of it. In fact, if you pass an object reference
as a parameter to any function, the task the compiler has to
perform to figure out whether or not it’s safe to destroy the object
after the function returns can easily become equivalent to the
Halting Problem. It’s something you just can’t automate.

Actually it’s trivial. A function either takes ownership of a resource
or not. This is part of the function signature. If it does, the
resource handle in the calling function is set to null. In the worst
case you’ll end up with a couple of redundant null checks which won’t
influence performance in any measurable way.

Even more trivially, you could make resources non-copyable by default so
you can’t pass them to other functions. This gives you more
compile-time checking, but it obviously doesn’t work in those cases
where you /do/ need to pass the resource to another part of the program.

Really, this is a solved problem in C++.

Well, I don’t like RAII. I think it’s a fundamentally flawed pattern that only
got invented at all because the C++ object model is badly flawed. But
if you simply can’t live without it, Barry Kelly (the Delphi compiler chief)
went and created a smart pointer implementation based on Delphi’s
Generics. So yes, the language does give you the facility to automate
it.

So Delphi is catching up with C++.

I’m sorry, but that’s extremely vague and sounds more like ranting than
anything useful. How is writing out information that makes a program
easier to read when you come back to it (or when someone else has
to maintain it) “useless”?

It doesn’t make the program easier to read.

Compare:
T *p;
p: TPointer;

The first is clearly a pointer to a T. The second could be anything;
you would have to look at the definition of TPointer to be sure.
Therefore the first is easier to read.On 4/8/2010 15:14, Mason Wheeler wrote:

----- Original Message ----
From: Rainer Deyke
Subject: Re: [SDL] Off-Topic: Quake II Ported to HTML5


Rainer Deyke - rainerd at eldwood.com

Even more trivially, you could make resources non-copyable by default so
you can’t pass them to other functions. This gives you more
compile-time checking, but it obviously doesn’t work in those cases
where you /do/ need to pass the resource to another part of the program.

Then you can throw object-oriented programming out the window, since
every non-static method takes Self (this) as a hidden first parameter and
can do whatever it wants to it, including “container.add(self)”.

I’m sorry, but that’s extremely vague and sounds more like ranting than
anything useful. How is writing out information that makes a program
easier to read when you come back to it (or when someone else has
to maintain it) “useless”?

It doesn’t make the program easier to read.

Compare:
T *p;
p: TPointer;

The first is clearly a pointer to a T. The second could be anything;
you would have to look at the definition of TPointer to be sure.
Therefore the first is easier to read.

First off, the first one is not “clearly” a pointer to a T. If there happened to
be an = sign or other operator before it, it’s a multiplication operation. We
can assume that it’s a pointer to a T because it’s at the beginning of the
line and T begins with a capital letter, but that’s just a convention to make
it more readable.

Readability conventions also exist in Delphi, and you actually mangled that one
pretty badly. If a type starts with a capital T, it’s assumed to be a special
data type. By convention, pointer types start with a capital P, and then the
name of whatever they’re pointing to. So a pointer to an integer is a
PInteger, and a pointer to a TMyRecord is a PMyRecord. (A pointer to a T
would be a PT, but nobody would declare a data type called T in Delphi.)
If you saw crap like that in the Delphi app you had to work with, that’s not
the language’s fault.

So which is easier to read? Something that might be a pointer declaration
or might be a multiplication operation, depending on what came before it, or
something in the dedicated variable declaration section of a procedure (no
operations allowed here, no ambiguity) whose name clearly specifies that
it’s a pointer and what it’s pointing to, per established convention?>----- Original Message ----

From: Rainer Deyke
Subject: Re: [SDL] Off-Topic: Quake II Ported to HTML5

Even more trivially, you could make resources non-copyable by default so
you can’t pass them to other functions. This gives you more
compile-time checking, but it obviously doesn’t work in those cases
where you /do/ need to pass the resource to another part of the program.

Then you can throw object-oriented programming out the window, since
every non-static method takes Self (this) as a hidden first parameter and
can do whatever it wants to it, including “container.add(self)”.

‘this’ is a non-owning pointer. You should not be able to use a
non-owning pointer to transfer ownership. The static type system should
prevent this (although it doesn’t in C++ or any other language I know of).

Shared ownership through reference counting is another possible
solution, albeit one with a high performance cost.

Compare:
T *p;
p: TPointer;

The first is clearly a pointer to a T. The second could be anything;
you would have to look at the definition of TPointer to be sure.
Therefore the first is easier to read.

First off, the first one is not “clearly” a pointer to a T. If there happened to
be an = sign or other operator before it, it’s a multiplication operation. We
can assume that it’s a pointer to a T because it’s at the beginning of the
line and T begins with a capital letter, but that’s just a convention to make
it more readable.

I don’t like the C pointer declaration syntax either, but that’s an
entirely different issue from having to define useless extra identifiers.

Don’t take my Delphi-bashing to mean that I like C or C++.On 4/8/2010 18:37, Mason Wheeler wrote:

----- Original Message ----
From: Rainer Deyke
Subject: Re: [SDL] Off-Topic: Quake II Ported to HTML5


Rainer Deyke - rainerd at eldwood.com

If you understand what the function does, you know its return type. If
you don’t understand what the function does, maybe you shouldn’t be
using it at all.

You’re still thinking like a code writer and not a code reader. Of course
you understand what the function does when you create it! But what if I
created it a year ago, then I went and took another job somewhere else,
and you get a CR to fix a bug that eventually takes you into the routine,
and you’ve never seen it before. Don’t you want as much explicit
information presented to you as possible, to aid you in figuring out what
this function is doing? With real code these days now spending more
than 60% of its life cycle in maintenance, not development, you’re
approaching this from a very counterproductive paradigm.

The function is called ‘max’. It’s one line long. How can there
possibly be any misunderstanding about what it does?

When reading code, I want the clarity that comes from removing
unnecessary clutter. If I really can’t figure out the type of a
variable, I can always ask my IDE.

Here is the max function in Python:

def max(x, y):
if x > y:
return x
else:
return y

Here it is in C++ with proprietary extensions:

template<class T0, class T1>
typeof(true ? T0() : T1()) max(T0 v0, T1 v1) {
return v0 > v1 ? v0 : v1;
}

Note how the extra clutter in the C++ version decreases readability
while adding absolutely no information of any kind.On 4/8/2010 15:27, Mason Wheeler wrote:

----- Original Message ----
From: Rainer Deyke
Subject: Re: [SDL] Off-Topic: Quake II Ported to HTML5


Rainer Deyke - rainerd at eldwood.com

The function is called ‘max’. It’s one line long. How can there
possibly be any misunderstanding about what it does?

I thought you were only giving illustrative examples, and not real,
useful code? Most functions that are actually useful are somewhere
between 5 and 20 lines long, and often have other function calls
in the function body. Some are much longer than that.

template<class T0, class T1>
typeof(true ? T0() : T1()) max(T0 v0, T1 v1) {
return v0 > v1 ? v0 : v1;
}

Holy crap! What in the world is that?!? I’ve seen a more readable
Max() implemented in assembly!

Note how the extra clutter in the C++ version decreases readability
while adding absolutely no information of any kind.

…which is why I prefer Pascal. It gets things like this right. Its
"extra clutter" increases readability by providing useful information.>----- Original Message ----

From: Rainer Deyke
Subject: Re: [SDL] Off-Topic: Quake II Ported to HTML5

Mason Wheeler wrote:

  • Manual memory management. W. T. F?
    Yes,
    And that automatically disqualifies it as a modern language, full stop.

That sentiment pretty well sums up a huge part of what’s wrong with
modern programming. People think that important tasks ought to be
done for them instead of learning how to do them right, so they take
what should be their own responsibility and hand it off to some
bloated library that’s not capable of doing it correctly. Anyone who
can’t manage memory themselves is incompetent, pure and simple,
and anyone who can but chooses not to for tasks more complicated
than scripts is a jerk who’s wasting my system resources.

Note, it is neither axiomatic that garbage collection must
be slow, nor is GC itself a technology associated only with
modern programming.

John McCarthy invented automatic garbage collection for Lisp
circa 1959.

He describes its implementation in his 1960 paper, Recursive
Functions of Symbolic Expressions and Their Computation by
Machine, Part I.

http://www-formal.stanford.edu/jmc/recursive.pdf

(see pp. 26-27)

At that time, his mark-and-sweep algorithm did indeed take
a lot of time to perform its function. (Note this was on
an IBM 704, whose CPU was able to execute “up to” 40,000
instructions per second.)

Nevertheless, McCarthy’s justification for employing the
GC technique, 50(!) years ago, will be familiar: “This
process, because it is entirely automatic, is more
convenient for the programmer than a system in which he has
to keep track of and erase un-wanted lists.”

Moving forward, some studies have shown modern generational
garbage collection systems being faster than the fastest
malloc/free implementations under real workloads:

http://www.ibm.com/developerworks/java/library/j-jtp09275.html

As to the idea that automating a task and 'doing it right’
must be in direct opposition, I politely reject the premise.
I think Olin Shivers said it best:

“I object to doing things that computers can do.”

Regards,

Bill>> From: Rainer Deyke

The function is called ‘max’. It’s one line long. How can there
possibly be any misunderstanding about what it does?

I thought you were only giving illustrative examples, and not real,
useful code? Most functions that are actually useful are somewhere
between 5 and 20 lines long, and often have other function calls
in the function body. Some are much longer than that.

‘max’ is an example, but it’s also a useful function I use very often.
Longer functions may benefit from optional type declarations, although I
would have to decide on a case-by-case basis.

template<class T0, class T1>
typeof(true ? T0() : T1()) max(T0 v0, T1 v1) {
return v0 > v1 ? v0 : v1;
}

Holy crap! What in the world is that?!? I’ve seen a more readable
Max() implemented in assembly!

Note how the extra clutter in the C++ version decreases readability
while adding absolutely no information of any kind.

…which is why I prefer Pascal. It gets things like this right. Its
"extra clutter" increases readability by providing useful information.

I challenge you to write a better version in Pascal, given these
requirements:

  • It accepts two arguments of possibly different numeric types.
  • Its return type has the following properties:
    • If both arguments use integer types, the larger integer type is
      used as return type.
    • If both arguments use floating point types, the larger floating
      point type is used as return type.
    • If one argument uses a floating point type and the other uses an
      integer type, the floating point type is used as return type.

The C++ version is hideously ugly, but at least it does the job.On 4/8/2010 22:09, Mason Wheeler wrote:

----- Original Message ----
From: Rainer Deyke
Subject: Re: [SDL] Off-Topic: Quake II Ported to HTML5


Rainer Deyke - rainerd at eldwood.com

Lisp is great.

One of the few languages to be able to beat optimized Fortran code (the
other is C++ when
used with template metaprogramming).

And if you can get your head around its syntax, you are in programmers’
nirvana. :slight_smile:

Nowadays there is no excuse for not offering some for of GC in a modern
language.

Objective-C: check (since version 2.0)
C++ : check (smart pointers and in C++11 the standard created a GC API,
providing a future path for a possible GC)
.Net environment: check (with APIs to do manual management if one so wishes)
JVM environment: check
D : check (with APIs to do manual management if one so wishes)
Modula-3: check (with APIs to do manual management if one so wishes, used to
write the Spin OS)
Oberon: check (with APIs to do manual management if one so wishes, used to
write the Oberon OS)
Component Pascal: check (with APIs to do manual management if one so wishes,
used to write OS)
Ada: check (with APIs to do manual management if one so wishes, used to
write OS)
Haskell: : check
Sing#: check (with APIs to do manual management if one so wishes, used to
write the Singularity OS)
Python, Perl, PHP, Ruby, Smalltak: check

My list is of course not exhaustive, but I guess it shows the trend. Any new
language coming today to the market has to provide some form
of memory management. If not, it is already doomed from the start.–
Paulo

On Fri, Apr 9, 2010 at 6:50 AM, Bill Kelly wrote:

Mason Wheeler wrote:

From: Rainer Deyke

  • Manual memory management. W. T. F?
    Yes,
    And that automatically disqualifies it as a modern language, full stop.

That sentiment pretty well sums up a huge part of what’s wrong with
modern programming. People think that important tasks ought to be
done for them instead of learning how to do them right, so they take
what should be their own responsibility and hand it off to some
bloated library that’s not capable of doing it correctly. Anyone who
can’t manage memory themselves is incompetent, pure and simple,
and anyone who can but chooses not to for tasks more complicated
than scripts is a jerk who’s wasting my system resources.

Note, it is neither axiomatic that garbage collection must
be slow, nor is GC itself a technology associated only with
modern programming.

John McCarthy invented automatic garbage collection for Lisp
circa 1959.

He describes its implementation in his 1960 paper, Recursive
Functions of Symbolic Expressions and Their Computation by
Machine, Part I.

http://www-formal.stanford.edu/jmc/recursive.pdf

(see pp. 26-27)

At that time, his mark-and-sweep algorithm did indeed take
a lot of time to perform its function. (Note this was on
an IBM 704, whose CPU was able to execute “up to” 40,000
instructions per second.)

Nevertheless, McCarthy’s justification for employing the
GC technique, 50(!) years ago, will be familiar: “This
process, because it is entirely automatic, is more
convenient for the programmer than a system in which he has
to keep track of and erase un-wanted lists.”

Moving forward, some studies have shown modern generational
garbage collection systems being faster than the fastest
malloc/free implementations under real workloads:

http://www.ibm.com/developerworks/java/library/j-jtp09275.html

As to the idea that automating a task and 'doing it right’
must be in direct opposition, I politely reject the premise.
I think Olin Shivers said it best:

“I object to doing things that computers can do.”

Regards,

Bill


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org

How does it do the job of selecting the return type? I haven’t seen
’typeof(true ? T0() : T1())’ before and it does not look like it functions
correctly (i.e. it smells). Which implementation of typeof are you using?
This makes more sense to me, though it forces another operator overload (+)
for objects:
template<class T0, class T1>
auto max(T0 v0, T1 v1) -> typeof(v0 + v1);

Jonny DOn Thu, Apr 8, 2010 at 10:30 PM, Rainer Deyke wrote:

template<class T0, class T1>
typeof(true ? T0() : T1()) max(T0 v0, T1 v1) {
return v0 > v1 ? v0 : v1;
}

  • Its return type has the following properties:
    • If both arguments use integer types, the larger integer type is
      used as return type.
    • If both arguments use floating point types, the larger floating
      point type is used as return type.
    • If one argument uses a floating point type and the other uses an
      integer type, the floating point type is used as return type.

The C++ version is hideously ugly, but at least it does the job.

I guess he is using the GCC extension

http://gcc.gnu.org/onlinedocs/gcc/Typeof.htmlOn Fri, Apr 9, 2010 at 10:15 AM, Jonathan Dearborn wrote:

On Thu, Apr 8, 2010 at 10:30 PM, Rainer Deyke wrote:

template<class T0, class T1>
typeof(true ? T0() : T1()) max(T0 v0, T1 v1) {
return v0 > v1 ? v0 : v1;
}

  • Its return type has the following properties:
    • If both arguments use integer types, the larger integer type is
      used as return type.
    • If both arguments use floating point types, the larger floating
      point type is used as return type.
    • If one argument uses a floating point type and the other uses an
      integer type, the floating point type is used as return type.

The C++ version is hideously ugly, but at least it does the job.

How does it do the job of selecting the return type? I haven’t seen
’typeof(true ? T0() : T1())’ before and it does not look like it functions
correctly (i.e. it smells). Which implementation of typeof are you using?
This makes more sense to me, though it forces another operator overload (+)
for objects:

template<class T0, class T1>
auto max(T0 v0, T1 v1) -> typeof(v0 + v1);

Jonny D


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org

T0 --> the first type
T0() --> a (default-initialized) value of the first type
T0 --> the second type
T1() --> a (default-initialized) value of the second type
true ? T0() : T1() --> a value of the common type of T0 and T1
typeof(true ? T0() : T1()) --> the common type of T0 and T1

The type of a ‘a ? b : c’ expression is the common type to which both
’a’ and ‘b’ can be converted. Therefore the type of 'a ? T0() : T1()'
is the common type of T0 and T1. ‘a’ must be a boolean, but its value
is irrelevant since I am only interested in the type of the expression,
not its value.On 4/9/2010 02:15, Jonathan Dearborn wrote:

On Thu, Apr 8, 2010 at 10:30 PM, Rainer Deyke wrote:

template<class T0, class T1>
typeof(true ? T0() : T1()) max(T0 v0, T1 v1) {
return v0 > v1 ? v0 : v1;
}

  • Its return type has the following properties:
    • If both arguments use integer types, the larger integer type is
      used as return type.
    • If both arguments use floating point types, the larger floating
      point type is used as return type.
    • If one argument uses a floating point type and the other uses an
      integer type, the floating point type is used as return type.

The C++ version is hideously ugly, but at least it does the job.

How does it do the job of selecting the return type? I haven’t seen
’typeof(true ? T0() : T1())’ before and it does not look like it functions
correctly (i.e. it smells). Which implementation of typeof are you using?


Rainer Deyke - rainerd at eldwood.com

Hmm. I guess I’ve never considered how the type from that operator is
handled statically. Cool.

Jonny DOn Fri, Apr 9, 2010 at 12:48 PM, Rainer Deyke wrote:

On 4/9/2010 02:15, Jonathan Dearborn wrote:

On Thu, Apr 8, 2010 at 10:30 PM, Rainer Deyke wrote:

template<class T0, class T1>
typeof(true ? T0() : T1()) max(T0 v0, T1 v1) {
return v0 > v1 ? v0 : v1;
}

  • Its return type has the following properties:
    • If both arguments use integer types, the larger integer type is
      used as return type.
    • If both arguments use floating point types, the larger floating
      point type is used as return type.
    • If one argument uses a floating point type and the other uses an
      integer type, the floating point type is used as return type.

The C++ version is hideously ugly, but at least it does the job.

How does it do the job of selecting the return type? I haven’t seen
’typeof(true ? T0() : T1())’ before and it does not look like it
functions
correctly (i.e. it smells). Which implementation of typeof are you
using?

T0 --> the first type
T0() --> a (default-initialized) value of the first type
T0 --> the second type
T1() --> a (default-initialized) value of the second type
true ? T0() : T1() --> a value of the common type of T0 and T1
typeof(true ? T0() : T1()) --> the common type of T0 and T1

The type of a ‘a ? b : c’ expression is the common type to which both
’a’ and ‘b’ can be converted. Therefore the type of 'a ? T0() : T1()'
is the common type of T0 and T1. ‘a’ must be a boolean, but its value
is irrelevant since I am only interested in the type of the expression,
not its value.


Rainer Deyke - rainerd at eldwood.com


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org