Whither C++? food for thought

From Stroustrup, Sutter, and Dos Reis
(https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Introduction%20to%20type%20and%20resource%20safety.pdf)

How do you feel ATS compares with “good” C++, in particular:

(1) "Adding finalizers to GC partially addresses this problem but even
something as simple and well-known as calling fclose() at GC time is
almost always unsuitable. Finalizers:

  • are executed at a nondeterministic time on a system thread, which
    can inject deadlocks with program threads;
  • are not guaranteed to run at all, so there is no guarantee the file
    will ever be closed as long as the program is running;
  • can lead to excessive resource retention when the GC is not run
    frequently; and
  • can make GC unnecessarily expensive if a finalizer can place a
    reference to an object it was supposed to destroy in an accessible
    location (“resurrection”).
    Consequently, the use of finalizers for resource cleanup is now
    actively discouraged in the major GC environments that support them,
    leaving the release of non-memory resources as a manual activity."

(2) “Using owner and static analysis is unmanageable for “ordinary
C-style code”. We would simply need too many owner annotation so that
those annotations would themselves become a nuisance and a source of
errors. This has been seen in languages depending on annotations (such
as Cyclone and Microsoft’s SAL annotations for C and C++ code) and our
own experiments. To be manageable on an industrial scale, owner
annotation must be rare. We ensure that by “burying” them in proper
ownership abstractions, such as vectors and unique_ptrs.”

(3) “As for dangling pointers and for ownership, this model detects
all possible errors. This means that we can guarantee that a program
is free of uses of invalidated pointers. There are many control
structures in C++, addresses of objects can appear in many guises
(e.g., pointers, references, smart pointers, iterators), and objects
can “live” in many places (e.g., local variables, global variables,
standard containers, and arrays on the free store). Our tool
systematically considers all combinations. Needless to say, that
implies a lot of careful implementation work (described in detail in
[Sutter,2015]), but it is in principle simple: all uses of invalid
pointers are caught.”

(4) “Note that this is still local analysis; we just look at a
function declaration with respect to possible invalidation. When
invalidation is possible, we must be conservative and assume the
worst. We consider whole-program analysis incompatible with the needs
of large programs, the need for a fast debug cycle, and the needs of a
language supporting dynamic linking. Whole-program can be useful, and
we will use it, but not in the usual debug cycle.”

(5) “This technique of moving objects rather than copying them has
been used for decades, but only infrequently and unsystematically. The
notion of direct language support for move semantics was pioneered by
Howard Hinnant and is part of C++11. The use of move semantics allows
us to move a large object implemented as a handle from scope to scope
without overhead and without resorting to error-prone explicit use of
pointers and explicit memory management.
One implication of having move semantics is that we can completely
encapsulate the management of non-scoped memory.”

(6) “For most resources (memory, locks, file handles, etc.),
acquisition can fail so a conventional resource manager must be able
to report an error. The only fully general mechanism for that is
throwing an exception if resource acquisition fails, and that’s what
the standard containers do. This scheme handles nested objects, class
hierarchies, and containers simply and efficiently. However, there are
also schemes that rely on explicit error handling. Those are brittle
and rarely generalize; the best basically hand- simulate RAII.”

(7) “The model presented so far relies on sequential execution in a
language with lexical scoping. In a multi- threaded system, we need to
handle more cases.
Our rule set for concurrency is not yet fully developed. In
particular, detached threads with pointers to shared data can be
tricky. However, threads that are joined at the end of their scopes
can be analyzed much as called functions and shared_ptrs can be used
to keep data alive for threads with less well- behaved lifetimes.
There are also obvious opportunities for tools that analyze for race
conditions and deadlocks.”

(8) “Here, we will only briefly mention other ways of breaking the C++
type system. These problems are well known and have well-known
solutions, so we will not address them here. Misuse of unions and
casts can lead to type and memory violations (so follow the rules that
prevent that [Stroustrup,2015]). For example, use a variant class
rather than a plain union. Out-of-range access and access through a
null pointer can lead to type and memory errors (so follow the rules
that prevent that). In particular, use array_view and not_null from
the Guideline Support Library (GSL) [Sutter, 2015b]. To minimize range
errors, we also recommend using a make_array() function that returns
an owner<array_view> to allocate an array on the free store, rather
than using new or malloc() directly. The aim of the Code Guidelines is
to eliminate a large range of errors by mutually supportive rules. No
one rule can by itself prevent a large class of errors: ban one misuse
and others will become popular (this is often referred to as “playing
whack-a-mole”). Thus, our ideal is a large set of mutually supportive
rules that together deliver type and memory safety guarantees.”

Nice analysis. :slight_smile:

When I say C++ is complex, it is a bit like saying that politics is complex.

When I say ATS is complex, I mostly mean that mathematics is complex.On Friday, March 4, 2016 at 5:43:29 PM UTC-5, gmhwxi wrote:

I feel that coding in C++ is too complex and coding in “good” C++
is even more complex. No matter how wonderful a feature is, a programmer
has to learn it. IMHO, any programmer has only a limited learning capacity.

Yes, ATS is complex, too. But there is at least a collection of typing
rules in ATS that type-theorists can grasp without too much difficulty.
More importantly, the consistency of these typing rules is formally
established.
You probably could not say the same about C++.

On Friday, March 4, 2016 at 3:24:08 PM UTC-5, Raoul Duke wrote:

From Stroustrup, Sutter, and Dos Reis
(
https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Introduction%20to%20type%20and%20resource%20safety.pdf)

How do you feel ATS compares with “good” C++, in particular:

(1) "Adding finalizers to GC partially addresses this problem but even
something as simple and well-known as calling fclose() at GC time is
almost always unsuitable. Finalizers:

  • are executed at a nondeterministic time on a system thread, which
    can inject deadlocks with program threads;
  • are not guaranteed to run at all, so there is no guarantee the file
    will ever be closed as long as the program is running;
  • can lead to excessive resource retention when the GC is not run
    frequently; and
  • can make GC unnecessarily expensive if a finalizer can place a
    reference to an object it was supposed to destroy in an accessible
    location (“resurrection”).
    Consequently, the use of finalizers for resource cleanup is now
    actively discouraged in the major GC environments that support them,
    leaving the release of non-memory resources as a manual activity."

(2) “Using owner and static analysis is unmanageable for “ordinary
C-style code”. We would simply need too many owner annotation so that
those annotations would themselves become a nuisance and a source of
errors. This has been seen in languages depending on annotations (such
as Cyclone and Microsoft’s SAL annotations for C and C++ code) and our
own experiments. To be manageable on an industrial scale, owner
annotation must be rare. We ensure that by “burying” them in proper
ownership abstractions, such as vectors and unique_ptrs.”

(3) “As for dangling pointers and for ownership, this model detects
all possible errors. This means that we can guarantee that a program
is free of uses of invalidated pointers. There are many control
structures in C++, addresses of objects can appear in many guises
(e.g., pointers, references, smart pointers, iterators), and objects
can “live” in many places (e.g., local variables, global variables,
standard containers, and arrays on the free store). Our tool
systematically considers all combinations. Needless to say, that
implies a lot of careful implementation work (described in detail in
[Sutter,2015]), but it is in principle simple: all uses of invalid
pointers are caught.”

(4) “Note that this is still local analysis; we just look at a
function declaration with respect to possible invalidation. When
invalidation is possible, we must be conservative and assume the
worst. We consider whole-program analysis incompatible with the needs
of large programs, the need for a fast debug cycle, and the needs of a
language supporting dynamic linking. Whole-program can be useful, and
we will use it, but not in the usual debug cycle.”

(5) “This technique of moving objects rather than copying them has
been used for decades, but only infrequently and unsystematically. The
notion of direct language support for move semantics was pioneered by
Howard Hinnant and is part of C++11. The use of move semantics allows
us to move a large object implemented as a handle from scope to scope
without overhead and without resorting to error-prone explicit use of
pointers and explicit memory management.
One implication of having move semantics is that we can completely
encapsulate the management of non-scoped memory.”

(6) “For most resources (memory, locks, file handles, etc.),
acquisition can fail so a conventional resource manager must be able
to report an error. The only fully general mechanism for that is
throwing an exception if resource acquisition fails, and that’s what
the standard containers do. This scheme handles nested objects, class
hierarchies, and containers simply and efficiently. However, there are
also schemes that rely on explicit error handling. Those are brittle
and rarely generalize; the best basically hand- simulate RAII.”

(7) “The model presented so far relies on sequential execution in a
language with lexical scoping. In a multi- threaded system, we need to
handle more cases.
Our rule set for concurrency is not yet fully developed. In
particular, detached threads with pointers to shared data can be
tricky. However, threads that are joined at the end of their scopes
can be analyzed much as called functions and shared_ptrs can be used
to keep data alive for threads with less well- behaved lifetimes.
There are also obvious opportunities for tools that analyze for race
conditions and deadlocks.”

(8) “Here, we will only briefly mention other ways of breaking the C++
type system. These problems are well known and have well-known
solutions, so we will not address them here. Misuse of unions and
casts can lead to type and memory violations (so follow the rules that
prevent that [Stroustrup,2015]). For example, use a variant class
rather than a plain union. Out-of-range access and access through a
null pointer can lead to type and memory errors (so follow the rules
that prevent that). In particular, use array_view and not_null from
the Guideline Support Library (GSL) [Sutter, 2015b]. To minimize range
errors, we also recommend using a make_array() function that returns
an owner<array_view> to allocate an array on the free store, rather
than using new or malloc() directly. The aim of the Code Guidelines is
to eliminate a large range of errors by mutually supportive rules. No
one rule can by itself prevent a large class of errors: ban one misuse
and others will become popular (this is often referred to as “playing
whack-a-mole”). Thus, our ideal is a large set of mutually supportive
rules that together deliver type and memory safety guarantees.”

I feel that coding in C++ is too complex and coding in “good” C++
is even more complex. No matter how wonderful a feature is, a programmer
has to learn it. IMHO, any programmer has only a limited learning capacity.

Yes, ATS is complex, too. But there is at least a collection of typing
rules in ATS that type-theorists can grasp without too much difficulty.
More importantly, the consistency of these typing rules is formally
established.
You probably could not say the same about C++.On Friday, March 4, 2016 at 3:24:08 PM UTC-5, Raoul Duke wrote:

From Stroustrup, Sutter, and Dos Reis
(
https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Introduction%20to%20type%20and%20resource%20safety.pdf)

How do you feel ATS compares with “good” C++, in particular:

(1) "Adding finalizers to GC partially addresses this problem but even
something as simple and well-known as calling fclose() at GC time is
almost always unsuitable. Finalizers:

  • are executed at a nondeterministic time on a system thread, which
    can inject deadlocks with program threads;
  • are not guaranteed to run at all, so there is no guarantee the file
    will ever be closed as long as the program is running;
  • can lead to excessive resource retention when the GC is not run
    frequently; and
  • can make GC unnecessarily expensive if a finalizer can place a
    reference to an object it was supposed to destroy in an accessible
    location (“resurrection”).
    Consequently, the use of finalizers for resource cleanup is now
    actively discouraged in the major GC environments that support them,
    leaving the release of non-memory resources as a manual activity."

(2) “Using owner and static analysis is unmanageable for “ordinary
C-style code”. We would simply need too many owner annotation so that
those annotations would themselves become a nuisance and a source of
errors. This has been seen in languages depending on annotations (such
as Cyclone and Microsoft’s SAL annotations for C and C++ code) and our
own experiments. To be manageable on an industrial scale, owner
annotation must be rare. We ensure that by “burying” them in proper
ownership abstractions, such as vectors and unique_ptrs.”

(3) “As for dangling pointers and for ownership, this model detects
all possible errors. This means that we can guarantee that a program
is free of uses of invalidated pointers. There are many control
structures in C++, addresses of objects can appear in many guises
(e.g., pointers, references, smart pointers, iterators), and objects
can “live” in many places (e.g., local variables, global variables,
standard containers, and arrays on the free store). Our tool
systematically considers all combinations. Needless to say, that
implies a lot of careful implementation work (described in detail in
[Sutter,2015]), but it is in principle simple: all uses of invalid
pointers are caught.”

(4) “Note that this is still local analysis; we just look at a
function declaration with respect to possible invalidation. When
invalidation is possible, we must be conservative and assume the
worst. We consider whole-program analysis incompatible with the needs
of large programs, the need for a fast debug cycle, and the needs of a
language supporting dynamic linking. Whole-program can be useful, and
we will use it, but not in the usual debug cycle.”

(5) “This technique of moving objects rather than copying them has
been used for decades, but only infrequently and unsystematically. The
notion of direct language support for move semantics was pioneered by
Howard Hinnant and is part of C++11. The use of move semantics allows
us to move a large object implemented as a handle from scope to scope
without overhead and without resorting to error-prone explicit use of
pointers and explicit memory management.
One implication of having move semantics is that we can completely
encapsulate the management of non-scoped memory.”

(6) “For most resources (memory, locks, file handles, etc.),
acquisition can fail so a conventional resource manager must be able
to report an error. The only fully general mechanism for that is
throwing an exception if resource acquisition fails, and that’s what
the standard containers do. This scheme handles nested objects, class
hierarchies, and containers simply and efficiently. However, there are
also schemes that rely on explicit error handling. Those are brittle
and rarely generalize; the best basically hand- simulate RAII.”

(7) “The model presented so far relies on sequential execution in a
language with lexical scoping. In a multi- threaded system, we need to
handle more cases.
Our rule set for concurrency is not yet fully developed. In
particular, detached threads with pointers to shared data can be
tricky. However, threads that are joined at the end of their scopes
can be analyzed much as called functions and shared_ptrs can be used
to keep data alive for threads with less well- behaved lifetimes.
There are also obvious opportunities for tools that analyze for race
conditions and deadlocks.”

(8) “Here, we will only briefly mention other ways of breaking the C++
type system. These problems are well known and have well-known
solutions, so we will not address them here. Misuse of unions and
casts can lead to type and memory violations (so follow the rules that
prevent that [Stroustrup,2015]). For example, use a variant class
rather than a plain union. Out-of-range access and access through a
null pointer can lead to type and memory errors (so follow the rules
that prevent that). In particular, use array_view and not_null from
the Guideline Support Library (GSL) [Sutter, 2015b]. To minimize range
errors, we also recommend using a make_array() function that returns
an owner<array_view> to allocate an array on the free store, rather
than using new or malloc() directly. The aim of the Code Guidelines is
to eliminate a large range of errors by mutually supportive rules. No
one rule can by itself prevent a large class of errors: ban one misuse
and others will become popular (this is often referred to as “playing
whack-a-mole”). Thus, our ideal is a large set of mutually supportive
rules that together deliver type and memory safety guarantees.”