I have just returned from Santa Cruz where we held the semiannual 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 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
(1 C++)
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)
andif (!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.
Reg Charney
References
[1] The Boost Library: www.boost.org
This article was originally published on the ACCU USA website in November 2002 at http://www.accu-usa.org/2002-11.html
Thanks to Reg for allowing us to reprint it.