|
by Reg. Charney
I have just returned from Santa Cruz where we held the
semi-annual Standards meeting on C++. I spent most of my time in the
Library Working Group discussing new features for the next Library
Extensions Technical Report. I have reported on some of the main
topics below.
Move semantics
Move semantics promises to significantly increase run-time
efficiency of many library elements — in some cases by a factor of
10+. The proposal has generated a lot of discussion been for years
and is finally bearing fruit. It originates in the Core Working
Group but mainly affects the Library. Changes are 100% compatible
with existing code, while introducing a fundamental new concept into
C++.
This proposal augments copy semantics. A user might define a
class as copyable and movable, either or neither. The difference
between a copy and a move is that a copy leaves the source
unchanged. A move on the other hand leaves the source in a state
defined differently for each type. The state of the source may be
unchanged, or it may be radically different. The only requirement is
that the object remain in a self consistent state (all internal
invariants are still intact). From a client code point of view,
choosing move instead of copy means that you don't care what happens
to the state of the source. For plain old data structures (PODs),
move and copy are identical operations (right down to the machine
instruction level).
This paper proposes the introduction of a new type of reference
that will bind to an rvalue:
struct A {/*...*/};
void foo(A&& i); // new syntax |
The && is the token which identifies the reference as an
“rvalue reference” (bindable to an rvalue) and thus
distinguishes it from our current reference syntax (using a single
&).
The rvalue reference is a new type, distinct from the current
(lvalue) reference. Functions can be overloaded on A& and
A&&, requiring such functions each to have distinct
signatures.
The most common overload set anticipated is:
void foo(const A&
t); // #1
void foo(A&& t); // #2 |
The rules for overload resolution are (in addition to the current
rules):
- rvalues prefer rvalue references.
- lvalues prefer lvalue references.
- CV qualification conversions are considered secondary relative
to r-/l-value conversions. rvalues can still bind to a const
lvalue reference (const A&), but only if there is not a more
attractive rvalue reference in the overload set. lvalues can
bind to an rvalue reference, but will prefer an lvalue reference
if it exists in the overload set. The rule that a more cv-qualified
object can not bind to a less cv-qualified reference stands -
both for lvalue and rvalue references.
Examples:
struct A {};
A source();
const A const_source();
// . . .
A a;
const A ca;
foo(a);
// binds to #1
foo(ca);
// binds to #1
foo(source()); // binds
to #2
foo(const_source()); // binds to #1 |
The first foo() call prefers the lvalue reference as (lvalue)a is
a better match for const A& than for A&& (lvalue ->
rvalue conversion is a poorer match than A& -> const A&
conversion). The second foo() call is an exact match for #1. The
third foo() call is an exact match for #2. The fourth foo() call can
not bind to #2 because of the disallowed const A&& ->
A&& conversion. But it will bind to #1 via an
rvalue->lvalue conversion.
Note that without the second foo() overload, the example code
works with all calls going to #1. As move semantics are introduced,
the author of foo knows that he will be attracting non-const rvalues
by introducing the A&& overload and can act accordingly.
Indeed, the only reason to introduce the overload is so that special
action can be taken for non-const rvalues.
Tuples
Tuples are fixed-size heterogeneous containers containing any
number of elements. They are a generalized form of std::pair. The
proposal originates in the Core Working Group from a Boost Library
[1] implementation, but it will mainly affect in the Library.
The proposed tuple types:
- Support a wider range of element types (e.g. reference types).
- Support input from and output to streams, customizable with
specific manipulators.
- Provide a mechanism for ‘unpacking’ tuple elements into
separate variables.
The tuple template can be instantiated with any number of
arguments from 0 to some predefined upper limit. In the Boost Tuple
library, this limit is 10. The argument types can be any valid C++
types. For example:
typedef
tuple< A,
const
B,
volatile
C,
const
volatile D
> t1; |
An n-element tuple has a default constructor, a constructor with
n parameters, a copy constructor and a converting copy constructor.
By converting copy constructor we refer to a constructor that can
construct a tuple from another tuple, as long as the type of each
element of the source tuple is convertible to the type of the
corresponding element of the target tuple. The types of the elements
restrict which constructors can be used:
- If an n-element tuple is constructed with a constructor taking
0 elements, all elements must be default constructible. For
example:
class no_default_ctor
{ no_default_ctor(); };
tuple<no_default_ctor, float> b; // error - need
default ctor
tuple<int&>
c;
// err no ref default ctor
tuple<int, float>
a;
// ok |
If an n-element tuple is constructed with a constructor taking n
elements, all elements must be copy constructible and convertible
(default initializable) from the corresponding argument. For
example:
tuple< int, const int,
std::string >(1, ’a’, "Hi") // ok
tuple< int, std::string >(1,
2);
// error |
- If an n-element tuple is constructed with the converting copy
constructor, each element type of the constructed tuple type
must be convertible from the corresponding element type of the
argument.
tuple< char, int, const
char(&)[3] > t1(’a’, 1, "Hi");
tuple< int, float, std::string > t2 =
t1;
// ok |
The argument to this constructor does not actually have to be of
the standard tuple type, but can be any tuple-like type that acts
like the standard tuple type, in the sense of providing the same
element access interface. For example, std::pair is such a
tuple-like type. For example:
| tuple< int, int
> t3 = make_pair(’a’,1);// ok |
The proposal includes tuple constructors, utility functions, and
ways of testing for tuples. The I/O streams library will be updated
to support input and output of tuples. For example,
|
cout <<
make_tuple(1,"C++"); |
outputs
Hash Tables
Hash tables almost made it into the first issue of the Standard,
but our deadline precluded doing due diligence on the proposal at
the time.
The current proposal is what you would expect and is compatible
with the three main library implementations for hash tables: SGI/STLport,
Dinkumware, and Metrowerks. What is new is that things like
max_factor, load_factor, and other constants are now treated as
hints. Each implementation is free to deal with them as it sees fit.
Also, most double values and parameters are going to be float. It
was felt that only one or two significant digits were meaningful, so
the extra space needed by double was wasted.
There was one major outstanding issue. Since there are already
widespread hash library implementations in use, how do we avoid
breaking existing code. Proposals included making the new hash
library names slightly different, or placing them in different
namespaces, or usurping the current names because only the committee
has the authority to place things in namespace std (any private
implementation that placed their hash library into std was, by
definition, in error.)
Polymorphic Function Object Wrapper
This proposal is based on the Boost Function Template Library
[1]. It was accepted in Santa Cruz for inclusion in the next TR of
the standard. It introduces a class template that generalizes the
notion of a function pointer to subsume function pointers, member
function pointers, and arbitrary function objects while maintaining
similar syntax and semantics to function pointers.
This proposal defines a pure library extension, requiring no
changes to the core C++ language. A new class template function is
proposed to be added into the standard header <functional>
along with supporting function overloads. A new class template
reference_wrapper is proposed to be added to the standard header
<utility> with two supporting functions. For example,
int add(int x, int y)
{ return x+y; }
bool adjacent(int x, int y)
{ return x == y-1 || x == y+1;}
struct compare_and_record
{
std::vector< std::pair<int,int> > values;
bool operator()(int x, int y)
{
values.push_back(
std::make_pair(x,y));
return x == y;
}
};
function< int (int, int) >
f;
f = &add;
cout << f(2, 3) <<
endl; // outputs 5
f = minus<int>();
cout << f(2, 3) <<
endl; // outputs -1 |
The proposed function object wrapper supports only a subset of
the operations supported by function pointers with slightly
different syntax and semantics. The major differences are detailed
below, but can be summarized as follows:
- Relaxation of target requirements to allow conversions in
arguments and result type.
- Lack of comparison operators. However, checking if (f) and if
(!f) is allowed.
- Lack of extraneous null-checking syntax.
The function class template is always a function object.
Conclusions
This short report did not touch on other exciting new features
that will likely appear in the language, including the Regular
Expression proposal, the use of static assertions to allow more
extensive compile-time checking based on type information, and the
auto expressions proposal to simplify template function definitions.
As you can see from the wide range of topics we discussed in
Santa Cruz, C++ is still an exciting language that is continuing to
adapt to users needs.
References
The Boost Library (www.boost.org)
By Reg. Charney
At the C++ ANSI/ISO Standards Committee last month, Andy Koenig
gave a technical presentation that I believe heralds a sea change in
C++’s future.
C++ is a spin-off of C and introduced classes back around 1983.
C++ reflects the needs and environment of computing at that time.
Things have changed significantly since then. We now have computers
that are magnitudes faster, having thousands of times more memory
and storage. The standard user and developer environment is no
longer the command line, but is rich multimedia experience. Many of
our newer applications run across networks that did not exist when C
was first implemented.
Under current and future environments, many of our old
assumptions are no longer valid or significant. For example, writing
small, fast programs was a point of pride from the original days of
computing. Now, writing a C/C++ program of less than 100KB can be
difficult, often due to the mandatory libraries that are included.
In the past, we wrote programs to run in closed environments on
isolated uniprocessors. Today, all that has changed. Now, security
is a major concern, and by extension, so is reliability. Network
considerations play into any design and newer languages are often
better suited to particular applications.
Also, the area served by C and C++ are diverging. C continues to
serve its core user base, but C++ is now used more and more for
large systems where its multi-paradigm, object oriented, and generic
programming features are a real advantage. While source code
compatibility is still a part of the C++ standardization effort, we
are coming to a time when library and extension differences are
going to force source incompatibility between the two languages. The
silver lining is that we will still have binary compatibility when
compiled under the same operating system.
This isn’t your grandmother’s C/C++ world any more.
The Power of
Events—An Introduction to Complex Event Processing In
Distributed Systems by David Luckham, Addison-Wesley,
ISBN 0-201-72789-7
Rating: Very highly recommended!
Reading Level: Easy.
I believe this book heralds the next step in system design,
reliability, and effectiveness. On every page, new ideas jumped out
at me.
In today’s ever more complex world with computer systems trying
to model this complexity, we often do not have the tools to
understand what is happening. Because of this ignorance and lack of
ability to effectively measure what is occurring, many of our major
systems are failing and we are unable to choose effective and
efficient ways of improving the process. This book shows how to
master this and future complexity.
The book comes in two parts, an introduction; and a more rigorous
approach. The first part contains examples from a variety of
distributed application fields: stock trading, protocol handling,
order processing, etc. The problems are non-trivial. The central
concept is an event. An event is an envelop that encloses a message,
one or more time stamps and an indication of the cause of the event.
From this starting point, we can track when something happened and
why. For example, shipping a package involves receiving an order,
doing a credit check, billing, and physically shipping the goods.
Such an example shows a simple event pattern for shipping goods.
Using this pattern, we can search for goods shipped and goods
rejected. Where the event pattern match fails indicates why it
failed to ship. Reporting on these patterns and the patterns
themselves are views of interest to different groups — all based
on the same set of events.
(Caveat—I am so taken with this book, that I have asked if I
can co-author the next in the series of these texts. Thus, take my
comments with a grain of salt, but my enthusiasm must indicate that
this is something special.)
— Reg. Charney
By Reg. Charney
Things have been pretty gloomy recently, so the slight up-tick
that is shown in this month’s graph is interesting. While there
has been a slight downturn in the total number of IT jobs available,
both in the U.S. and Silicon Valley (SV), the opening for software
engineers seems to be bucking this trend. (see Figure #1).

Least we get too excited, the graph shows that we are currently
running at about 18% U.S.-wide and 5% of Silicon Valley of the job
openings that we had in January 2000.
|