Unpopular C++ ideas

139 views
Skip to first unread message

John Nagle

unread,
Mar 21, 2004, 5:07:52 PM3/21/04
to
Here are a few ideas for the next major revision of C++
that people will hate, but which contain some sense.

1. Move unsafe library functions to new header files.

The headers for the classically unsafe string
functions ("sprintf", "strcat", etc.) would be moved
to "<unsafe-string.h>", or something similar.

This breaks existing programs, but they're probably
broken anyway. After 25 years of buffer overflows,
it's time to dump these badly designed functions.

2. Make "assert" part of the core language.

Compilers should know more about "assert", so they
can optimize and pull asserts out of loops. Early
assertion failure detection, where programs can report an
assertion failure as soon as it becomes inevitable,
should be encouraged.

Example:

const int tabsize = 100
int tab[tabsize];
for (int i=0; i<=tabsize ; i++)
{ assert(i<tabsize); tab[i] = 0; }

can be optimized into

assert(false);


3. "swap" and "move" as primitives.

STL collections should all support "swap" and "move",
and collections should be able to handle objects for which
"swap" and "move" are defined, but "operator=" is not.
This moves towards collections of auto_ptr.

4. "let".

"let" declares and initializes a variable with the type
of the right hand side.

Example:

let x = 1.0; // x is a double

And, of course

for (let p = tab.begin(); p != tab.end(); p++) { ... }

which is shorter and more generic than the current form

for (vector<sometype>::iterator p = tab.begin(); p != tab.end(); p++) { ... }

5. Automatic class locking

Like "synchronized" classes in Java. Only one thread can be
"inside" an object at a time. Lock upon entry to a public
function, unlock at exit.

This requires some clear thinking about what it means for
control to be "inside" an object. I'd suggest that if this is done,
it should be possible to temporarily "leave" the object by
writing, in a class member function:

void classname::fn()
{ ... // locked
public {
... // unlocked
}; // relocking
}

If an object needs to block a thread and let other threads
in during a block, that syntax allows it in a straightforward
manner.

John Nagle
Animats

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

Edward Diener

unread,
Mar 21, 2004, 10:01:40 PM3/21/04
to
John Nagle wrote:
> Here are a few ideas for the next major revision of C++
> that people will hate, but which contain some sense.
>
> 1. Move unsafe library functions to new header files.
>
> The headers for the classically unsafe string
> functions ("sprintf", "strcat", etc.) would be moved
> to "<unsafe-string.h>", or something similar.
>
> This breaks existing programs, but they're probably
> broken anyway. After 25 years of buffer overflows,
> it's time to dump these badly designed functions.

What purpose does this server other than to make a judgment call on what is
"safe" or "un-safe" ? Since these functions are in their own C header file,
I see no purpose in moving them somewhere else.

>
> 2. Make "assert" part of the core language.
>
> Compilers should know more about "assert", so they
> can optimize and pull asserts out of loops. Early
> assertion failure detection, where programs can report an
> assertion failure as soon as it becomes inevitable,
> should be encouraged.
>
> Example:
>
> const int tabsize = 100
> int tab[tabsize];
> for (int i=0; i<=tabsize ; i++)
> { assert(i<tabsize); tab[i] = 0; }
>
> can be optimized into
>
> assert(false);

What happens when an assert is false if you make it part of the language
itself ? Currently the result is up to the macro and implementation, Making
it part of the language, which I do not necessarily deem a bad idea, must
define exactly what it does when an assert is false. Whatever is decided may
not be everyone's choice.

>
>
> 3. "swap" and "move" as primitives.
>
> STL collections should all support "swap" and "move",
> and collections should be able to handle objects for which
> "swap" and "move" are defined, but "operator=" is not.
> This moves towards collections of auto_ptr.

One can have collections of boost::shared_ptr so not having collections of
std::auto_ptr is not a big deal as I see it. Furthermore boost::shared_ptr
has been accepted in TR1.

By swap and move, I believe you are arguing for move semantics not invoking
a copy constructor or assignment operator. I will let others argue that one
out as I have never seen the practical advantage of it.

>
> 4. "let".
>
> "let" declares and initializes a variable with the type
> of the right hand side.
>
> Example:
>
> let x = 1.0; // x is a double
>
> And, of course
>
> for (let p = tab.begin(); p != tab.end(); p++) { ... }
>
> which is shorter and more generic than the current form
>
> for (vector<sometype>::iterator p = tab.begin(); p != tab.end(); p++)
> { ... }

This I do like, especially for the purposes of template programming, where
the programmer needs to create a variable of a type which will hold the type
of the rvalue expression. The compiler does know the type of the rvalue
expression, so having it automatically create a variable of the same type is
a no-brainer. I believe there are a number of proposals which move the next
version of C++ in the direction of supporting the "type_of" idea, and would
be surprised if the next version of C++ diod not support some form of
"type_of".

>
> 5. Automatic class locking
>
> Like "synchronized" classes in Java. Only one thread can be
> "inside" an object at a time. Lock upon entry to a public
> function, unlock at exit.

It needs to be better than Java's "synchronized" in order to allow a single
member function to be locked and not an entire object as Java does, which is
crude. Again I believe that the next version of C++ is considering the
threading issue, but members of the C++ standard committee will know about
that, and hopefully will comment.

>
> This requires some clear thinking about what it means for
> control to be "inside" an object. I'd suggest that if this is done,
> it should be possible to temporarily "leave" the object by
> writing, in a class member function:
>
> void classname::fn()
> { ... // locked
> public {
> ... // unlocked
> }; // relocking
> }
>
> If an object needs to block a thread and let other threads
> in during a block, that syntax allows it in a straightforward
> manner.

Except for the first idea, which is a prejudice, the rest are all
interesting.

Andrei Alexandrescu

unread,
Mar 22, 2004, 10:19:27 PM3/22/04
to
"John Nagle" <na...@animats.com> wrote in message
news:c627c.40806$qC6....@newssvr25.news.prodigy.com...

> 2. Make "assert" part of the core language.
>
> Compilers should know more about "assert", so they
> can optimize and pull asserts out of loops.

They already know pretty much all that's to be known. The general-purpose
optimizer can easily detect the condition and figure out whether it's always
true or always false.

Andrei

John Nagle

unread,
Mar 23, 2004, 5:31:31 PM3/23/04
to
You can go much further than that. Compilers should be able
to hoist and strength-reduce asserts, not just evaluate
them at compile time. But the compiler needs to know that
it's OK to fail an assert "early". That is,

int tab[100];
int n;
...
for (int i=0; i<n; i++)
{ assert(i<100);
tab[i] = 0;
}

should be compiled as

int tab[100];
int n;
...
assert(n < 100); // hoisted assert
for (int i=0; i<n; i++)
{ // assert(i<100); // implied by assert above
tab[i] = 0;
}

even though the assertion will fail before the loop is even entered.
The compiler needs to know that early assertion failure is permitted,
so it can hoist asserts through loop entries as shown above.

With this, it becomes feasible to put asserts in collection classes
for subscript checking. Some British work on Pascal in the 1980s showed
that well over 95% of subscript checks can be optimized out using
techniques like this.

Niklas Matthies

unread,
Mar 23, 2004, 5:31:34 PM3/23/04
to
On 2004-03-23 03:19, "Andrei Alexandrescu" wrote:
> "John Nagle" <na...@animats.com> wrote in message
> news:c627c.40806$qC6....@newssvr25.news.prodigy.com...
>> 2. Make "assert" part of the core language.
>>
>> Compilers should know more about "assert", so they
>> can optimize and pull asserts out of loops.
>
> They already know pretty much all that's to be known. The
> general-purpose optimizer can easily detect the condition and figure
> out whether it's always true or always false.

The standard could allow implementations to assume that the condition
of an assert is always true when NDEBUG is defined, and perform
optimizations based on this assumption. In other words, let any assert
with a condition that would evaluate to false be formally undefined
behavior when NDEBUG is defined.

You can already get the effect with something like

(condition || <expression that always invokes undefined behavior>)

if the compiler is smart enough, but it would be nice to have this
more explicitly via assert (and hence more likely to be actually
exploited by compilers).

-- Niklas Matthies

Helium

unread,
Mar 24, 2004, 4:15:02 AM3/24/04
to
> > 4. "let".
> >
> > "let" declares and initializes a variable with the type
> > of the right hand side.
> >
> > Example:
> >
> > let x = 1.0; // x is a double
> >
> > And, of course
> >
> > for (let p = tab.begin(); p != tab.end(); p++) { ... }
> >
> > which is shorter and more generic than the current form
> >
> > for (vector<sometype>::iterator p = tab.begin(); p != tab.end(); p++)
> > { ... }
>
> This I do like, especially for the purposes of template programming, where
> the programmer needs to create a variable of a type which will hold the type
> of the rvalue expression. The compiler does know the type of the rvalue
> expression, so having it automatically create a variable of the same type is
> a no-brainer. I believe there are a number of proposals which move the next
> version of C++ in the direction of supporting the "type_of" idea, and would
> be surprised if the next version of C++ diod not support some form of
> "type_of".
>
Isn't let very similar to the proposed auto:
http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2004/n1607.pdf

Hans Aberg

unread,
Mar 24, 2004, 4:16:21 AM3/24/04
to
In article <c627c.40806$qC6....@newssvr25.news.prodigy.com>,
na...@animats.com (John Nagle) wrote:

>1. Move unsafe library functions to new header files.
>
> The headers for the classically unsafe string
>functions ("sprintf", "strcat", etc.) would be moved
>to "<unsafe-string.h>", or something similar.

One should probably instead have a keyword like "pure" or something, that
indicates a function is safe (re-entrant). A function is pure if made up
by pure components, and a function indicated pure cannot in its
implementation call non-pure components unless explicitly overridden say
with the same keyword "pure".

> This breaks existing programs, but they're probably
>broken anyway. After 25 years of buffer overflows,
>it's time to dump these badly designed functions.

Then one does not need to break existing programs.

Hans Aberg

Walter

unread,
Mar 24, 2004, 6:42:42 PM3/24/04
to

"John Nagle" <na...@animats.com> wrote in message
news:c627c.40806$qC6....@newssvr25.news.prodigy.com...
> Here are a few ideas for the next major revision of C++
> that people will hate, but which contain some sense.
>
> 1. Move unsafe library functions to new header files.
>
> The headers for the classically unsafe string
> functions ("sprintf", "strcat", etc.) would be moved
> to "<unsafe-string.h>", or something similar.
>
> This breaks existing programs, but they're probably
> broken anyway. After 25 years of buffer overflows,
> it's time to dump these badly designed functions.

D has an unusual approach to this kind of problem. There's the keyword
'deprecated' which can be applied to declarations that have been superceded,
but are still necessary to support legacy code. Then, a compiler switch is
used to allow or disallow use of deprecated declarations.

This makes it easy for the maintenance programmer to find and purge any
dependencies on obsolete declarations, and easy for the library vendor to
provide a clear upgrade path.

One could do this in C++ now using an #ifdef and the appropriate convention.
The problem with conventions, of course, is getting them consistently
adopted. One could also simply comment out the declarations for sprintf in
stdio.h, but my experience with such methods are:

1) programmers are very, very reluctant to modify system or vender .h files.
2) when they do, it breaks some unrelated project
3) updating the compiler means that all one's tweaks get undone

-Walter
www.digitalmars.com free C/C++/D compilers

Bob Bell

unread,
Mar 25, 2004, 4:55:41 PM3/25/04
to
na...@animats.com (John Nagle) wrote in message news:<IQ%7c.13797$MY4....@newssvr27.news.prodigy.com>...

> You can go much further than that. Compilers should be able
> to hoist and strength-reduce asserts, not just evaluate
> them at compile time. But the compiler needs to know that
> it's OK to fail an assert "early". That is,
>
> int tab[100];
> int n;
> ...
> for (int i=0; i<n; i++)
> { assert(i<100);
> tab[i] = 0;
> }
>
> should be compiled as
>
> int tab[100];
> int n;
> ...
> assert(n < 100); // hoisted assert
> for (int i=0; i<n; i++)
> { // assert(i<100); // implied by assert above
> tab[i] = 0;
> }
>
> even though the assertion will fail before the loop is even entered.
> The compiler needs to know that early assertion failure is permitted,
> so it can hoist asserts through loop entries as shown above.

Except that this changes the meaning of the code. Before hoisting,
"tab" is filled before the assert fires; after hoisting, "tab" is not
filled at all. This doesn't seem like such a good change. Where an
assertion is placed by the programmer has everything to do with the
state of the program if/when the assertion fails. If the code is
written like the first version above but is compiled like the second
version, then when I examine the resulting core dump (or whatever)
I'll be plenty surprised to find that "tab" still has garbage, not 100
zeroes.

Bob

Steven T. Hatton

unread,
Mar 25, 2004, 4:56:41 PM3/25/04
to
Walter wrote:

> D has an unusual approach to this kind of problem. There's the keyword
> 'deprecated' which can be applied to declarations that have been
> superceded, but are still necessary to support legacy code. Then, a
> compiler switch is used to allow or disallow use of deprecated
> declarations.

Have you considered using the same approach to modify the way 'deprecated'
elements are compiled? For example, if a new version of the C++ standard
were created, and it 'broke' backward compatability, there might be a way
to persuade the compiler to process libraries from the older version
differently from code conforming to the latest standard.

Yes, the thought makes me nervous. It might turn out to be far to
complicated to actually implement, nonetheless, it seems worth considering.

--
STH

John Nagle

unread,
Mar 26, 2004, 10:44:52 PM3/26/04
to
Hans Aberg wrote:

> In article <c627c.40806$qC6....@newssvr25.news.prodigy.com>,
> na...@animats.com (John Nagle) wrote:
>
>
>>1. Move unsafe library functions to new header files.
>>
>> The headers for the classically unsafe string
>>functions ("sprintf", "strcat", etc.) would be moved
>>to "<unsafe-string.h>", or something similar.
>
>
> One should probably instead have a keyword like "pure" or something, that
> indicates a function is safe (re-entrant). A function is pure if made up
> by pure components, and a function indicated pure cannot in its
> implementation call non-pure components unless explicitly overridden say
> with the same keyword "pure".

That's a different issue.

By "unsafe", I meant "do not check buffer size before storing".
It's generally recognized that some of the original C standard
library functions were very badly designed, and I'm arguing that
the price we pay for their flaws is higher than the price of
fixing the code that uses them.

John Nagle
Animats

Andrei Alexandrescu

unread,
Mar 26, 2004, 10:51:07 PM3/26/04
to
"John Nagle" <na...@animats.com> wrote in message
news:IQ%7c.13797$MY4....@newssvr27.news.prodigy.com...
[about putting assert inside the language]

> You can go much further than that. Compilers should be able
> to hoist and strength-reduce asserts, not just evaluate
> them at compile time. But the compiler needs to know that
> it's OK to fail an assert "early". That is,
>
[snip example]

>
> even though the assertion will fail before the loop is even entered.
> The compiler needs to know that early assertion failure is permitted,
> so it can hoist asserts through loop entries as shown above.
>
> With this, it becomes feasible to put asserts in collection classes
> for subscript checking. Some British work on Pascal in the 1980s showed
> that well over 95% of subscript checks can be optimized out using
> techniques like this.

But this all can be done if we inline assert as "if (!cond) throw
AssertFailed;" or something similar.

What I am saying is that it is not building assert into the language that is
the issue here. It is building flow analysis into the language. Then,
whether you have assert, an if follwoed by abort() or by throwing an
exception, or whatever, the flow analysis will take care of it
appropriately.

There's nothing specal about assert. It is just that in certain builds it is
an if statement that terminates the program execution on one branch.
Standard flow analysis can take care of that whether it is in the form of an
assert or a hand-coded if.

Andrei Alexandrescu

unread,
Mar 26, 2004, 10:51:32 PM3/26/04
to
"Niklas Matthies" <usenet...@nmhq.net> wrote in message
news:slrnc614dq.2r7...@nmhq.net...

> On 2004-03-23 03:19, "Andrei Alexandrescu" wrote:
> > "John Nagle" <na...@animats.com> wrote in message
> > news:c627c.40806$qC6....@newssvr25.news.prodigy.com...
> >> 2. Make "assert" part of the core language.
> >>
> >> Compilers should know more about "assert", so they
> >> can optimize and pull asserts out of loops.
> >
> > They already know pretty much all that's to be known. The
> > general-purpose optimizer can easily detect the condition and figure
> > out whether it's always true or always false.
>
> The standard could allow implementations to assume that the condition
> of an assert is always true when NDEBUG is defined, and perform
> optimizations based on this assumption. In other words, let any assert
> with a condition that would evaluate to false be formally undefined
> behavior when NDEBUG is defined.
>
> You can already get the effect with something like
>
> (condition || <expression that always invokes undefined behavior>)
>
> if the compiler is smart enough, but it would be nice to have this
> more explicitly via assert (and hence more likely to be actually
> exploited by compilers).

What's wrong with abort() instead the "expression that always invokes
undefined behavior"? Again, there is nothing special about assert. If you
handcode something similar to an assertion, you should be able to achieve
similar effects.

Buikding assert in the language would be the wrong, limited, horizonless way
to go about it.


Andrei

Walter

unread,
Mar 29, 2004, 1:02:28 AM3/29/04
to

""Steven T. Hatton"" <hat...@globalsymmetry.com> wrote in message
news:66qdnaOhp-d...@speakeasy.net...

> Walter wrote:
>
> > D has an unusual approach to this kind of problem. There's the keyword
> > 'deprecated' which can be applied to declarations that have been
> > superceded, but are still necessary to support legacy code. Then, a
> > compiler switch is used to allow or disallow use of deprecated
> > declarations.
>
> Have you considered using the same approach to modify the way 'deprecated'
> elements are compiled? For example, if a new version of the C++ standard
> were created, and it 'broke' backward compatability, there might be a way
> to persuade the compiler to process libraries from the older version
> differently from code conforming to the latest standard.
>
> Yes, the thought makes me nervous. It might turn out to be far to
> complicated to actually implement, nonetheless, it seems worth
considering.

Most C++ compilers (and C compilers) have command line switches to change
the semantics of the language to be compiled. After all, there are 3 primary
versions of C, and there's a lot of C++ code out there written to older C++
semantics that still need to be supported.

The downside of all that is, let's say, the compiler has switches to alter n
different language semantics. Then the testing has to test n! (n factorial)
permutations of the compiler. This rapidly approaches a mathematical
impracticality.

John Nagle

unread,
Mar 29, 2004, 1:03:50 AM3/29/04
to
It's very similar to the "auto" proposal, but there
are some issues with using the "auto" keyword.

Although it's rarely done, you can declare variables
"auto". You can write

auto int x = 1;

just as you can write

static int x = 1;

So there's syntatic trouble with "auto" in that placement.
The compiler would have to distinguish

auto x = 1;

from all of the above.

Incidentally,

auto const int x = 1;
and
const auto int x = 1;
and even
const auto volatile int x = 1;
are all legal. So it's possible to keep the parse ambiguous for
quite a while.

It may be possible to get a parser to buy another overload
of "auto", but it's not a comfortable syntax.

Admittedly "let" adds a keyword, but everybody will understand
what it means. Overloading "auto" is rather obscure. Bear in
mind that this is a feature for general C++ programmers, not
l33t template gods.

John Nagle
Animats

John Nagle

unread,
Mar 29, 2004, 1:05:32 AM3/29/04
to
The committee is considering a special
built-in compile time assertion form to make the template
people happy. Maybe that could be extended to cover
the general case.

The compile-time form causes the compile to fail,
even if the code involved is never executed. So that's
an "early failure", much as I'm discussing here.

The point I'm making about "assert" is that if the
compiler knows more about it, it can optimize it
much more aggresively. See the (snipped) example.

What I'm trying to get to is low-cost optimized subscript
checking, such as a few advanced Pascal compilers
had twenty years ago. See

http://doi.acm.org/10.1145/201059.201063

John Nagle
Animats

Andrei Alexandrescu wrote:

---

Niklas Matthies

unread,
Mar 29, 2004, 1:05:47 AM3/29/04
to
On 2004-03-27 03:51, "Andrei Alexandrescu" wrote:
> "Niklas Matthies" <usenet...@nmhq.net> wrote in message:
:

>> You can already get the effect with something like
>>
>> (condition || <expression that always invokes undefined behavior>)
>>
>> if the compiler is smart enough, but it would be nice to have this
>> more explicitly via assert (and hence more likely to be actually
>> exploited by compilers).
>
> What's wrong with abort() instead the "expression that always
> invokes undefined behavior"?

The fact that abort() doesn't invoke undefined behavior. :)

Consider:

// A
try { f(); }
catch (some_exception const &) { /* ignore */ }
catch (...) { abort(); }

vs.

// B
try { f(); }
catch (some_exception const &) { /* ignore */ }
catch (...) { assert(false); }

In B, the assert would tell the compiler that it may assume that the
only exceptions that the invocation of f() can throw are of type
some_exception, so the generated exception-catching code could omit
checking whether the exception coming from f() actually is a
some_exception or not. It could effectively compile to

try { f(); }
catch (...) { /* ignore */ }

in non-debug mode.

The compiler is not allowed to this in A, because the behavior would
be different when f throws a non-some_exception.

Furthermore, when the compiler is able to statically detect that the
invocation of f() actually _does_ throw a non-some_exception, it would
be allowed to refuse compilation of B. With A, even a warning would be
considered inappropriate by many users. Or do you want each and every
occurrence of abort() in non-dead code to trigger a warning?

:


> Buikding assert in the language would be the wrong, limited,
> horizonless way to go about it.

Actually, what I'd like see to be built into the language is
something like __assume_true(cond) (which would be defined to be a
no-op when cond would evaluate to true if it were evaluated at that
point, and to be undefined behavior when it would evaluate to false),
which then could be used in the definition of the assert() macro,
along the lines of:

#ifdef NDEBUG
#define assert(cond) __assume_true(cond)
#else
#define assert(cond) ((void) ((cond) || __assertion_failure(#cond)))
#endif

extern "C" { void __noreturn__ __assertion_failure(char const *); }

-- Niklas Matthies

Hans Aberg

unread,
Mar 29, 2004, 1:05:59 AM3/29/04
to
In article <AOR8c.42971$%05....@newssvr25.news.prodigy.com>,
na...@animats.com (John Nagle) wrote:

> By "unsafe", I meant "do not check buffer size before storing".
>It's generally recognized that some of the original C standard
>library functions were very badly designed, and I'm arguing that
>the price we pay for their flaws is higher than the price of
>fixing the code that uses them.

I believe that the C++ suggested usage is to not use those old C-functions
at all, but the corresponding C++ string functions. If those C functions
should be changed, that is probably a C-language issue, which C++ then
would follow.

On another level, one can think of admitting language construct that
admits the compiler to do static checking for accuracy instead of dynamic
checks, for safe implementation efficiency. If somebody figures out how to
do that, that technique could as well be applied to the old C functions.

Hans Aberg

Colin Hirsch

unread,
Mar 29, 2004, 1:08:14 PM3/29/04
to
Niklas Matthies wrote:
> [...]

> The standard could allow implementations to assume that the condition
> of an assert is always true when NDEBUG is defined, and perform
> optimizations based on this assumption. In other words, let any assert
> with a condition that would evaluate to false be formally undefined
> behavior when NDEBUG is defined.

Hi,

I use assert(), or rather a more powerful handcrafted version ASSERT(),
to assert internal invariants of my program. In any non-trivial, more
than 1000 lines of code, I do not believe that I can possibly have
enough black-box or unit tests to cover _all_ possible combinations of
flow through the code with all kinds of inputs. Hence the _last_ thing
that I want is to disable asserts in production and have the software
continue to run with inconsistent data, instead of a controlled crash
with a stack trace and core dump. In other words, for my paranoia,
NDEBUG is an absolute no-no for 99% of all code/projects...

Apart from that I would like to follow Andrei's reasoning that enhancing
flow analysis in the compiler is better than optimising for one special
macro (which is probably not used in its "raw" form all that often
anyhow)(and disregarding the question of how much compilers can
eliminate with current optimisations when NDEBUG is defined).

Regards, Colin

t...@cs.ucr.edu

unread,
Mar 29, 2004, 1:08:35 PM3/29/04
to
Niklas Matthies <usenet...@nmhq.net> wrote:
[...]
+ The standard could allow implementations to assume that the condition
+ of an assert is always true when NDEBUG is defined, and perform
+ optimizations based on this assumption. In other words, let any assert
+ with a condition that would evaluate to false be formally undefined
+ behavior when NDEBUG is defined.
+
+ You can already get the effect with something like
+
+ (condition || <expression that always invokes undefined behavior>)
+
+ if the compiler is smart enough, but it would be nice to have this
+ more explicitly via assert (and hence more likely to be actually
+ exploited by compilers).

Wow. I like your proposal *very* much.

Suppose that a given C++ implementation were modified so that:
(1) When NDEBUG is defined, "assert(<exp>)" expands to say
"(<exp>||*0)"
(2) Since all's fair when "<exp>" is false, the expression
"(<exp>||*0)" generates no code, and
(3) the implementation simply assumes that following the
evaluation of "(<exp>||*0)" the expression "<exp>" is true.

It would seem that:
- By the as-if rule, the modified implementation would continue to
conform as much as the original did.
- No existing unbroken code would get broken.
- No existing code would slow down.
- Some existing code would actually speed up if the implemenation
took advantage of (3).

Am I missing something?

Tom Payne

Thorsten Ottosen

unread,
Mar 29, 2004, 1:09:35 PM3/29/04
to
"John Nagle" <na...@animats.com> wrote in message
news:9g99c.43288$Ku5....@newssvr25.news.prodigy.com...

> The committee is considering a special
> built-in compile time assertion form to make the template
> people happy. Maybe that could be extended to cover
> the general case.

That would be nice. FYI, I'm now the person responsible for
writing that proposal. My first
paper will be available on the Sydney post-mailing.

> The point I'm making about "assert" is that if the
> compiler knows more about it, it can optimize it
> much more aggresively. See the (snipped) example.
>
> What I'm trying to get to is low-cost optimized subscript
> checking, such as a few advanced Pascal compilers
> had twenty years ago.

Although I don't discuss it much in my paper, the second version of my
proposal will.
Let us assume that vector::operator[] was declared (not defined) like this:

vector::operator[]( size_type n )
precondition { n < size() : throw range_error(); };

That might make it easy for compilers to detect when range checking
is not necessary in loops.

best regards

Thorsten

Pete Forman

unread,
Mar 29, 2004, 1:09:44 PM3/29/04
to
na...@animats.com (John Nagle) writes:

> 1. Move unsafe library functions to new header files.

It is often far from clear which header files are being called owing
to indirect inclusion. It might be better to add a deprecated tag to
individual functions. (I am not offering a syntax for this.)
--
Pete Forman -./\.- Disclaimer: This post is originated
WesternGeco -./\.- by myself and does not represent
pete....@westerngeco.com -./\.- opinion of Schlumberger, Baker
http://petef.port5.com -./\.- Hughes or their divisions.

Andrei Alexandrescu

unread,
Mar 29, 2004, 8:53:17 PM3/29/04
to
"John Nagle" <na...@animats.com> wrote in message
news:9g99c.43288$Ku5....@newssvr25.news.prodigy.com...

> The committee is considering a special
> built-in compile time assertion form to make the template
> people happy. Maybe that could be extended to cover
> the general case.

Kewl.

> The point I'm making about "assert" is that if the
> compiler knows more about it, it can optimize it
> much more aggresively. See the (snipped) example.
>
> What I'm trying to get to is low-cost optimized subscript
> checking, such as a few advanced Pascal compilers
> had twenty years ago. See
>
> http://doi.acm.org/10.1145/201059.201063

I understand your point; mine seems to not be as well understood. Again, my
point is, it's not assert that is special. The flow analysis of a test (if
statement) that terminates the current function (or the whole program)
abruptly on one of its branches - that's what's important.

There is no code sample you can show me that could benefit of a built-in
assert, than of a compiler performing standard flow analysis. If we manage
to get that point across, then the next step is to convince that flow
analysis is more general and has more applicabilities than a good assert.

Oh, and by the way, forgive this little joke:

A: Top posting.
Q: What's the worst thing on the Usenet?

:o)

Andrei

Andrei Alexandrescu

unread,
Mar 29, 2004, 8:53:34 PM3/29/04
to
"Niklas Matthies" <usenet...@nmhq.net> wrote in message
news:slrnc6avc2.372...@nmhq.net...

> On 2004-03-27 03:51, "Andrei Alexandrescu" wrote:
> > "Niklas Matthies" <usenet...@nmhq.net> wrote in message:
> :
> >> You can already get the effect with something like
> >>
> >> (condition || <expression that always invokes undefined behavior>)
> >>
> >> if the compiler is smart enough, but it would be nice to have this
> >> more explicitly via assert (and hence more likely to be actually
> >> exploited by compilers).
> >
> > What's wrong with abort() instead the "expression that always
> > invokes undefined behavior"?
>
> The fact that abort() doesn't invoke undefined behavior. :)
>
> Consider:
>
> // A
> try { f(); }
> catch (some_exception const &) { /* ignore */ }
> catch (...) { abort(); }
>
> vs.
>
> // B
> try { f(); }
> catch (some_exception const &) { /* ignore */ }
> catch (...) { assert(false); }

The two examples should be equivalent.

Andrei

t...@cs.ucr.edu

unread,
Mar 30, 2004, 11:09:50 AM3/30/04
to
t...@cs.ucr.edu wrote:
+ Niklas Matthies <usenet...@nmhq.net> wrote:
+ [...]
+ + The standard could allow implementations to assume that the condition
+ + of an assert is always true when NDEBUG is defined, and perform
+ + optimizations based on this assumption. In other words, let any assert
+ + with a condition that would evaluate to false be formally undefined
+ + behavior when NDEBUG is defined.

+ +
+ + You can already get the effect with something like
+ +
+ + (condition || <expression that always invokes undefined behavior>)
+ +
+ + if the compiler is smart enough, but it would be nice to have this
+ + more explicitly via assert (and hence more likely to be actually
+ + exploited by compilers).
[...]
+ Suppose that a given C++ implementation were modified so that:
+ (1) When NDEBUG is defined, "assert(<exp>)" expands to say
+ "(<exp>||*0)"
+ (2) Since all's fair when "<exp>" is false, the expression
+ "(<exp>||*0)" generates no code, and
+ (3) the implementation simply assumes that following the
+ evaluation of "(<exp>||*0)" the expression "<exp>" is true.
+
+ It would seem that:
+ - By the as-if rule, the modified implementation would continue to
+ conform as much as the original did.
+ - No existing unbroken code would get broken.
+ - No existing code would slow down.
+ - Some existing code would actually speed up if the implemenation
+ took advantage of (3).
+
+ Am I missing something?

Oops! Of course, a program that violates its assertions can conform,
even when NDEBUG is defined.

But, if the standards were simply modified so that the result of
evaluating "assert(<exp>)" is undefined whenever (1) "<exp>" is false
and (2) NDEBUG is defined, then:

* Conforming implementations would continue to conform, i.e., an
occurrence of "assert(<exp>)" would not need to generate
any behavior when NDEBUG is defined.

* Programs that don't violate any assertions would retain their
former behavior and performance, even when NDEBUG is defined.

* Following an occurrence of "assert(<exp>)" a conforming
implementation could behave as though "<exp>" were true,
even when NDEBUG is defined.

* Therefore, under aggressively optimized implementations, some
programs that don't violate their assertions would actually run
faster than before.

Presumably, aggressively optimized implementations already attach no
behaviour to occurrences of "(<exp>||*0)" and simply assume that
"<exp>" is true afterward. Such implementations could simply expand
"assert(<exp>)" as "(<exp>||*0)" whenever NDEBUG is defined, and
thereby take advantage of optimizations already in place.

Risto Lankinen

unread,
Mar 30, 2004, 11:30:21 AM3/30/04
to

""Andrei Alexandrescu"" <SeeWebsit...@moderncppdesign.com> wrote in
message news:c4aeq2$2h1ia4$1...@ID-14036.news.uni-berlin.de...

>
> I understand your point; mine seems to not be as well understood. Again,
my
> point is, it's not assert that is special. The flow analysis of a test (if
> statement) that terminates the current function (or the whole program)
> abruptly on one of its branches - that's what's important.

Could you please elaborate. This didn't help me understand your
point any better.

FWIW, I can't see how flow analysis could [easily] improve on
assert-aware compiler. Since the flow cannot always take all
invariants into consideration, generic functions must handle all
possible cases. If actual use is limited to a subset [e.g. adds
invariants] of the function's state space, how does the compiler
find this out using flow analysis, if one of the functions resides
in, say, a library?

- Risto -

Niklas Matthies

unread,
Mar 30, 2004, 1:28:08 PM3/30/04
to
On 2004-03-30 01:53, "Andrei Alexandrescu" wrote:
> "Niklas Matthies" <usenet...@nmhq.net> wrote in message
> news:slrnc6avc2.372...@nmhq.net...
>> On 2004-03-27 03:51, "Andrei Alexandrescu" wrote:
:

>> > What's wrong with abort() instead the "expression that always
>> > invokes undefined behavior"?
>>
>> The fact that abort() doesn't invoke undefined behavior. :)
>>
>> Consider:
>>
>> // A
>> try { f(); }
>> catch (some_exception const &) { /* ignore */ }
>> catch (...) { abort(); }
>>
>> vs.
>>
>> // B
>> try { f(); }
>> catch (some_exception const &) { /* ignore */ }
>> catch (...) { assert(false); }
>
> The two examples should be equivalent.

I don't know what you mean by "they should", but they certainly
aren't equivalent (with the proposed semantics of assert() in NDEBUG
mode), as I thought I demonstrated in the previous posting.

-- Niklas Matthies

Niklas Matthies

unread,
Mar 30, 2004, 1:28:34 PM3/30/04
to
On 2004-03-29 18:08, t...@cs.ucr.edu wrote:
> Niklas Matthies <usenet...@nmhq.net> wrote:
> [...]
>+ The standard could allow implementations to assume that the condition
>+ of an assert is always true when NDEBUG is defined, and perform
>+ optimizations based on this assumption. In other words, let any assert
>+ with a condition that would evaluate to false be formally undefined
>+ behavior when NDEBUG is defined.
>+
>+ You can already get the effect with something like
>+
>+ (condition || <expression that always invokes undefined behavior>)
>+
>+ if the compiler is smart enough, but it would be nice to have this
>+ more explicitly via assert (and hence more likely to be actually
>+ exploited by compilers).
>
> Wow. I like your proposal *very* much.
>
> Suppose that a given C++ implementation were modified so that:
> (1) When NDEBUG is defined, "assert(<exp>)" expands to say
> "(<exp>||*0)"
> (2) Since all's fair when "<exp>" is false, the expression
> "(<exp>||*0)" generates no code, and

It does generate code when <exp> produces side effects. I would prefer
<exp> to be never evaluated at runtime in NDEBUG mode, just as with
the current assert(). And this requires language support.

> (3) the implementation simply assumes that following the
> evaluation of "(<exp>||*0)" the expression "<exp>" is true.
>
> It would seem that:
> - By the as-if rule, the modified implementation would continue to
> conform as much as the original did.
> - No existing unbroken code would get broken.
> - No existing code would slow down.
> - Some existing code would actually speed up if the implemenation
> took advantage of (3).
>
> Am I missing something?

Apart from the above, I don't think so.

As I wrote in another post, I think it would be better to have a
new primitive that takes an expression that is convertible to bool
and tells the compiler "you may assume that if this expression would
be evaluated, the result converted to bool would be true".
While in many situations '<exp> || <undefined behavior>' is formally
equivalent, it qualifies as a rather obscure hack in my opinion.

-- Niklas Matthies

Risto Lankinen

unread,
Mar 31, 2004, 1:15:27 AM3/31/04
to

<t...@cs.ucr.edu> wrote in message news:c4b52f$1cd$1...@glue.ucr.edu...

> t...@cs.ucr.edu wrote:
> Presumably, aggressively optimized implementations already attach no
> behaviour to occurrences of "(<exp>||*0)" and simply assume that
> "<exp>" is true afterward.

What entitles the compiler to assume that <exp> is always true
in (<exp>||*0)?

- Risto -

Risto Lankinen

unread,
Mar 31, 2004, 1:15:43 AM3/31/04
to

"Niklas Matthies" <usenet...@nmhq.net> wrote in message
news:slrnc6j5re.31c...@nmhq.net...

>
> It does generate code when <exp> produces side effects. I would prefer
> <exp> to be never evaluated at runtime in NDEBUG mode, just as with
> the current assert(). And this requires language support.

This is good for backward compatibility with the current state,
but in my experience it has been a source of subtle bugs [that
the expression in assert() is not always evaluated]. If assert()
became a compiler feature, I would like the expression to be
evaluated (just to retain the side effects) even if NDEBUG is
defined.

- Risto -