A C++0x a készülő új C++ szabvány, amely felváltja a régi, 1998-ban publikált (C++98), majd 2003-ban frissített (C++03) szabványt. Az új szabvány újításokat hoz nyelvi magba és a könyvtárba egyaránt, nagyrészt a Technical Report 1 mentén haladva. Mivel a szabvány még nincs véglegesítve, a cikk egyes részei esetleg elavultak lehetnek. A legfrissebb jelentés, az N2800 2008 októberében jelent meg.

Bár a szabvány megjelenését 2009-re tervezték (és így a C++09 nevet kapta volna), valűszínűleg ez 2010-re csúszik.

Ahogy a C++ megalkotásakor, itt is figyeltek a kompatibilitási szempontokra. Bjarne Stroustrup (az eredeti nyelv megalkotója) bejelentése alapján várhatóan közel 100%-os visszafele kompatibilitás lesz a 2003-as szabvánnyal.

Az új szabvány megalkotásában szerepet játszó vezérelvek

szerkesztés

A bizottság az alábbi szempontokat tartotta szem előtt munkája során:

  • A kompatibilitás fenntartása a 1998-as szabvánnyal, és a amennyire lehet a C nyelvvel
  • Az újdonságokat inkább könyvtári funkcióként valósítsák meg, mint nyelvi elemként
  • A programozási technikát fejlesztő változtatások preferálása
  • Az alkalmazásspecifikus új funciók bevezetése helyett a rendszer és könyvtárépítés támogatásának még jobb kiépítése
  • A jelenlegi, típusbiztonságra veszélyes technikák helyett biztonságos alternatívák biztosítása
  • A teljesítmény növelése és a közvetlen hardwerelérés javítása
  • Valós problémákra való megoldások biztosítása
  • A "nulla overhead" elv betartása: ha egy funkciót nem használok a programban az ne fogyasszon semmilyen erőforrást
  • A C++ oktatásának könnyítése anélkül, hogy a nyelv funkcióit csökkentenék

Nyelvi kiegészítések

szerkesztés

A nyelv legfőbb változtatásai között szerepel a többszálúság és a generikus programozás valamint a teljesítmény javítása.

A leírásban a nyelvi újításokat négy csoportba soroltuk: futási és fordítási idejű teljesítményt valamint használhatóságot javító változtatások és új funkciók. A több csoportba is sorolható eseteket a leginkább jellemzőben említjük.

Futási idejű teljesítményt javító változtatások

szerkesztés

Ezek a nyelvi elemek a memória- vagy processzorhasználat csökkentését célozzák.

Jobbérték (rvalue) referencia és move-szemantika

szerkesztés

A C++ szabvány szerint a temporálisok (jobbértékek, mivel értékadásnak csak jobb oldalán állhatnak) átadhatók függvényparaméterként, de csak const & típusúként, azaz a hívott függvény nem tud különbséget tenni a valódi temporálisok és az így átadott normál objektumok között, valamint a const miatt megváltoztatni sem tudja azt, még ha tudja is, hogy az objektum a hívás után elpusztul.

Az új szabvány egy új referencia típust vezet be, a jobbérték-referenciát (rvalue reference). A definiálásához a typename && szintaxist kell használni. Ekkor nem-konstansként is át lehet adni őket, azaz a függvény tudja módosítani az objektumot. Ez az új paramétertípus segít a move-szemantika megvalósításában.

For example, a std::vector is, internally, a wrapper around a C-style array with a size. If a vector temporary is created or returned from a function, it can only be stored by creating a new vector and having it copy all of the R-value's data into it. Then the temporary is destroyed, deleting its data.

With R-value references, a "move constructor" of std::vector that takes an R-value reference to a vector can simply copy the pointer to the internal C-style array out of the R-value into the new vector, then leave the R-value in an empty state. There is no array copying, and the destruction of the empty temporary does not destroy the memory. The function returning a vector temporary need only return a std::vector<>&&. If vector has no move constructor, then the copy constructor will be invoked with a const std::vector<> & as normal. If it does have a move constructor, then the move constructor can be invoked, and significant memory allocation can be avoided.

For safety reasons a named variable will never be considered to be an R-value even if it's declared as such; in order to get an R-value the library function std::move() should be used.

bool is_r_value(int &&) { return true; }
bool is_r_value(const int &) { return false; }

void test(int && i)
{
    is_r_value(i); // false
    is_r_value(std::move(i)); // true
}

Due to the nature of the wording of R-value references, and to some modification to the wording for L-value references (regular references), R-value references allow developers to provide perfect function forwarding. When combined with variadic templates, this ability allows for function templates that can perfectly forward arguments to another function that takes those particular arguments. This is most useful for forwarding constructor parameters, to create factory functions that will automatically call the correct constructor for those particular arguments.

Általánosított konstans kifejezés

szerkesztés

A C++ szabvány ismeri a konstans kifejezést, amellyel a fordítási időben kiértékelhető kifejezéseket jelöli, de ezek köre meglehetősen korlátozott: semmilyen függvényhívás nem szerepelhet


C++ has always had the concept of constant expressions. These are expressions such as 3+4 that will always yield the same results and that have no side effects. Constant expressions are optimization opportunities for compilers, and compilers frequently execute them at compile time and store the results in the program. Also, there are a number of places where the C++ specification requires the use of constant expressions. Defining an array requires a constant expression, and enumerator values must be constant expressions.

However, constant expressions have always ended whenever a function call or object constructor was encountered. So something as simple as this is illegal:

int GetFive() {return 5;}

int some_value[GetFive() + 5]; //create an array of 10 integers. illegal C++

This is not legal C++, because GetFive() + 5 is not a constant expression. The compiler has no way of knowing if GetFive actually is constant at runtime. In theory, this function could affect a global variable, call other non-runtime constant functions, etc.

C++0x will introduce the keyword constexpr, which allows the user to guarantee that a function or object constructor is a compile-time constant. The above example can be rewritten as follows:

constexpr int GetFive() {return 5;}

int some_value[GetFive() + 5]; //create an array of 10 integers. legal C++0x

This allows the compiler to understand, and verify, that GetFive is a compile-time constant.

The use of constexpr on a function imposes very strict limitations on what that function can do. First, the function must have a non-void type. Second, the function contents must be of the form, "return expr". Third, expr must be a constant expression, after argument substitution. This constant expression may only call other functions defined as constexpr, or it may use other constant expression data variables. Lastly, a function with this label cannot be called until it is defined in this translation unit.

Variables can also be defined as constant expression values:

constexpr double accelerationOfGravity = 9.8;
constexpr double moonGravity = accelerationOfGravity / 6;

Constant expression data variables are implicitly const. They can only store the results of constant expressions or constant expression constructors.

In order to construct constant expression data values from user-defined types, constructors can also be declared with constexpr. A constant expression constructor must be defined before its use in the translation unit, as with constant expression functions. It must have an empty function body. It must initialize its members with constant expressions. And the destructors for such types should be trivial.

Copying constexpr constructed types should also be defined as a constexpr, in order to allow them to be returned by value from a constexpr function. Any member function of a class, such as copy constructors, operator overloads, etc, can be declared as constexpr, so long as they fit the definition for function constant expressions. This allows the compiler to copy classes at compile time, perform operations on them, etc.

A constant expression function or constructor can be called with non-constexpr parameters. Just as a constexpr integer literal can be assigned to a non-constexpr variable, so too can a constexpr function be called with non-constexpr parameters, and the results stored in non-constexpr variables. The keyword only allows for the possibility of compile-time constancy when all members of an expression are constexpr.

A POD (Plaing Old Data - a régi, C-típusú adatok) új meghatározása

szerkesztés

Bár sok adattípusnak hasznos volna, ha POD-nak minősülne (például a C-s könytárakkal való kommunikációban elengedhetetlenek), a C++03 szabályai túlságosa korlátozóak, ezért a C++0x lazít néhány szabályon.

Egy class/struct akkor minősül POD-nak, ha trivial, standard-layout, és minden adattagja POD.

A trivial jelentése:

  1. Triviális konstruktorral rendelkezik (használható az ~SomeClass() = default szintaxis)
  2. Triviális másoló konstruktorral rendelkezik
  3. Triviális értékadó operátorral rendelkezik
  4. Triviális, nem virtuális destruktorral rendelkezik

A standard-layout a következőképpen lett meghatározva:

  1. A nem statikus adattagjai, csak a standard-layout előírásokat teljesítők lehetnek
  2. Minden nem-statikus adattagnak azonos láthatósági szintje kell legyen
  3. Nincs virtuális függvénye
  4. Nincs virtuális bázisosztálya
  5. Minden bázis osztálya standard-layout tulajdonságú
  6. Az elsőként definiált nem-statikus tagja nem szerepel a szülői láncban/fában
  7. A leszármazási fában csak egy osztálynak lehet nem-statikus adattagja

Fordítási idejű teljesítményt javító változtatások

szerkesztés

Külső template

szerkesztés

A fordítónak minden esetben le kell példányosítani a templateket, ha egy teljesen meghatározott előfordulást talál, és ez feleslegesen megnövelheti a fordítási időt amikor ugyanazt a templatet ugyanazokkal a típusparaméterekkel kell példányosítania több fordítási egységben is. Az új szabvány kiterjeszti az extern kulcsszót templatekre is, ami kényszeríti a fordítót arra, hogy ne példányosítsa a templatet abban a fordítási egységben

extern template class std::vector<MyClass>;

Core language usability enhancements

szerkesztés

These features exist for the primary purpose of making the language easier to use. These can improve type safety, minimize code repetition, make it more difficult to erroneously use code, or something similar.

Initializer lists

szerkesztés

Standard C++ borrows the initializer list concept from C. The idea is that a struct or array can be created giving a list of arguments in the order of the members' definitions in the struct. These initializer lists are recursive, so an array of structs or struct containing other structs can use them. This is very useful for static lists or just for initializing a struct to a particular value. C++ has constructors, which can replicate the initialization of an object. But that alone does not replace all of the utility of this feature. Standard C++ allows this on structs and classes, except that these objects must conform to the Plain Old Data (POD) definition; non-POD classes cannot use initializer lists, nor can useful C++-style containers like std::vector.

C++0x will bind the concept of initializer lists to a type, called std::initializer_list. This allows constructors and other functions to take initializer lists as parameters. For example:

class SequenceClass
{
public:
  SequenceClass(std::initializer_list<int> list);
};

This will allow SequenceClass to be constructed from a sequence of integers, as such:

SequenceClass someVar = {1, 4, 5, 6};

This constructor is a special kind of constructor, called an initializer list constructor. Classes with such a constructor are treated specially during uniform initialization.

The class std::initializer_list<> is a first-class C++0x standard library type. However, they can only be initially constructed statically by the C++0x compiler through the use of the {} syntax. The list can be copied once constructed, though this is only a copy-by-reference. An initializer list is constant; its members cannot be changed once the initializer list is created, nor can the data in those members be changed.

Because initializer_list is a real type, it can be used in other places besides class constructors. Regular functions can take typed initializer lists as arguments. For example:

void FunctionName(std::initializer_list<float> list);

FunctionName({1.0f, -3.45f, -0.4f});

Standard containers can also be initialized this way:

vector<string> v = { "xyzzy", "plugh", "abracadabra" };

Uniform initialization

szerkesztés

Standard C++ has a number of problems with initializing types. There are several ways to initialize types, and they do not all produce the same results when interchanged. The traditional constructor syntax, for example, can look like a function declaration, and steps have to be taken to ensure that the compiler will not mistake it for such. Only aggregates and POD types can be initialized with aggregate initializers (using SomeType var = {/*stuff*/};).

C++0x will provide a syntax that allows for fully uniform type initialization that will work on any object. It expands on the initializer list syntax:

struct BasicStruct
{
 int x;
 float y;
};

struct AltStruct
{
  AltStruct(int _x, float _y) : x(_x), y(_y) {}

private:
  int x;
  float y;
};

BasicStruct var1{5, 3.2f};
AltStruct var2{2, 4.3f};

The initialization of var1 functions exactly as though it were a C-style initializer list. Each public variable will be initialized with each value of the initializer list. Implicit type conversion will be used where necessary, but if there is no type conversion available, compilation will fail.

The initialization of var2 simply calls the constructor.

The uniform initialization construct can remove the need for specifying certain types:

struct IdString
{
  std::string name;
  int identifier;
};

IdString var3{"SomeName", 4};

This syntax will automatically initialize the std::string with the const char * parameter. One is also able to do the following:

IdString GetString()
{
  return {"SomeName", 4}; //Note the lack of explicit type.
}

Uniform initialization will not replace constructor syntax. There will still be times when constructor syntax will be required. If a class has an initializer list constructor (TypeName(initializer_list<SomeType>);), then it takes priority over other forms of construction, if the initializer list conforms to the sequence constructor's type. The C++0x version of std::vector will have an initializer list constructor for its template type. This means that:

std::vector<int> theVec{4};

This will call the initializer list constructor, not the constructor of std::vector that takes a single size parameter and creates the vector with that size. To access this constructor, the user will need to use the standard constructor syntax directly.

Type inference

szerkesztés

In standard C++ (and C), the type of a variable must be explicitly specified in order to use it. However, with the advent of template types and template metaprogramming techniques, the type of something, particularly the well-defined return value of a function, may not be easily expressed. As such, storing intermediates in variables is difficult, possibly requiring knowledge of the internals of a particular metaprogramming library.

C++0x allows this to be mitigated in two ways. First, the definition of a variable with an explicit initialization can use the auto keyword. This creates a variable of the specific type of the initializer:

auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;

The type of someStrangeCallableType is simply whatever the particular template function override of boost::bind returns for those particular arguments. This type is easily determined procedurally by the compiler as part of its semantic analysis duties, but is not easy for the user to determine upon inspection.

The type of otherVariable is also well-defined, but it is easier for the user to determine. It is an int, which is the same type as the integer literal.

Additionally, the keyword decltype can be used to determine the type of an expression at compile-time. For example:

int someInt;
decltype(someInt) otherIntegerVariable = 5;

This is more useful in conjunction with auto, since the type of an auto variable is known only to the compiler. However, decltype can also be very useful for expressions in code that makes heavy use of operator overloading and specialized types.

auto is also useful for reducing the verbosity of the code. For instance, instead of writing

for (vector<int>::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)

the programmer can use the shorter

for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)

This difference grows as the programmer begins to nest containers, though in such cases typedefs are a good way to decrease the amount of code.

The type denoted by decltype is often different from the type deduced by auto.

#include <vector>

int main()
{
  const std::vector<int> v(1);
  auto a = v[0];      // a has type int  
  decltype(v[0]) b;   // b has type const int&, the return type of 
                      // std::vector<int>::operator[](size_type) const
  auto c = 0;         // c has type int   
  auto d = c;         // d has type int            
  decltype(c) e;      // e has type int, the type of the entity named by c 
  decltype((c)) f;    // f has type int&, because (c) is an lvalue
  decltype(0) g;      // g has type int, because 0 is an rvalue
}

Intervallum-alapú for ciklus

szerkesztés

A Boost könyvtár bevezeti az intervallum fogalmát: intervallum egy irányított lista két pontja közötti rész. Egy tárolóból vett két interátor például meghatározhat egy intervallumot. A könyvtár erősen építeni fog ezekre az intervallumokra, de a nyelvi elemek is bővülnek egy rájuk épülő lehetőséggel: a for kifejezés kényelmes lehetőséget ad az intervallumokon való végigiterálásra.

int my_array[5] = {1, 2, 3, 4, 5};
for(int &x : my_array)
{
  x *= 2;
}

A for első felében a használt változó szerepel, ami az értékeket fogja sorra felvenni, a : után pedig az éppen iterált intervallum szerepel. Ezek olyan objektumok, amelyek eleget tesznek a Range concept-nek.

Lambda functions and expressions

szerkesztés

In standard C++, particularly when used with C++ standard library algorithm functions such as sort and find, the user will often wish to define predicate functions near the place where they make the algorithm function call. The language has only one mechanism for this: the ability to define a class inside of a function. This is often cumbersome and verbose, as well as interrupting the flow of the code. Additionally, standard C++'s rules for classes defined in functions do not permit them to be used in templates, so using them is simply not possible.

The obvious solution would be to allow for the definition of lambda expressions and lambda functions. C++0x will allow for the definition of lambda functions.

A lambda function is defined as follows:

[](int x, int y) { return x + y; }

The return type of this unnamed function is decltype(x+y). The return type can only be omitted if the lambda function is of the form "return expression". This limits the lambda function to one statement.

The return type can be explicitly specified as follows, for a more complicated example:

[](int x, int y) -> int { int z = x + y; return z + x; }

In this example, a temporary variable, z, is created to store an intermediate. As with normal functions, the value of this intermediate is not held between invocations.

The return type can be omitted entirely if the lambda function does not return a value (i.e. if the return type is void).

References to variables defined in the same scope as the lambda function can be used as well. The set of variables of this sort is commonly called a closure. Closures are defined and used as follows:

std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
  total += x
});
std::cout << total;

This would display the total of all elements in the list. The variable total is stored as a part of the lambda function's closure. Since it is a reference to the stack variable total, it can change its value.

Closure variables for local variables can also be defined without the reference symbol &, which indicates that the lambda function will copy the value. This forces the user to declare their intent to reference local variables or copy them. If a closure object containing references to local variables is invoked after the innermost block scope of its creation, the behaviour is undefined.

For lambda functions that are guaranteed to run in the scope of its definition, it is possible to use all available stack variables without having to explicitly reference them:

std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
  total += x
});

The specific internal implementation can vary, but the expectation is that the lambda function will store the actual stack pointer of the function it is created in, rather than individual references to stack variables.

If [=] is used instead of [&], all referenced variables will be copied, allowing the lambda function to be used after the end of the lifetime of the original variables.

The defaults can also be combined with lists. For example, if the user wants to capture most variables by reference, but have one by value, then the user can do the following:

int total = 0;
int value = 5;
[&, value](int x) { total += (x * value) };

This will cause total to be stored as a reference, but value will be stored as a copy.

If a lambda function is defined by a member function of a class, it is assumed to be a friend of that class. Such lambda functions can use a reference to an object of the class type and access its internal members:

[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction() };

This will only work if the scope of this lambda function's creation is in a member function of SomeType.

The handling of the this pointer, referencing the object that the current member function is operating on, is special. It must be explicitly designated in the lambda function:

[this]() { this->SomePrivateMemberFunction() };

Using the [&] or [=] form of lambda functions will automatically make this available.

Lambda functions are function objects of a implementation-dependent type; this type's name is only available to the compiler. If the user wishes to take a lambda function as a parameter, the type must be a template type, or it must create a std::function to capture the lambda value. The use of the auto keyword can locally store the lambda function:

auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction() };

However, if the lambda function captures all of its closure variables by reference, or if it has no closure variables, then its type shall be publicly derived from std::reference_closure<R(P)>, where R(P) is the function signature with return type. This is expected to be a more efficient representation for lambda functions than capturing them in a std::function:

std::reference_closure<void()> myLambdaFunc = [this]() { this->SomePrivateMemberFunction() };
myLambdaFunc();

Alternate function syntax

szerkesztés

Standard C function declaration syntax was perfectly adequate for the feature set of the C language. As C++ evolved from C, it kept the basic syntax and extended it where necessary. However, as C++ became more complicated, it exposed a number of limitations, particularly with regard to template function declarations. The following, for example, is not allowable in C++03:

template< typename LHS, typename RHS> 
  Ret AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //Ret must be the type of lhs+rhs

The type Ret is whatever the addition of types LHS and RHS will produce. Even with the aforementioned C++0x functionality of decltype, this is not possible:

template< typename LHS, typename RHS> 
  decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //Not legal C++0x

This is not legal C++ because lhs and rhs have not yet been defined; they will not be valid identifiers until after the parser has parsed the rest of the function prototype.

To work around this, C++0x will introduce a new function definition and declaration syntax:

template< typename LHS, typename RHS> 
  auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}

This syntax can be used for more mundane function declarations and definitions:

struct SomeStruct
{
  auto FuncName(int x, int y) -> int;
};

auto SomeStruct::FuncName(int x, int y) -> int
{
  return x + y;
}

In C++, template classes and functions necessarily impose restrictions on the types that they take. For instance, the standard library containers require that the contained types be assignable. Unlike the dynamic polymorphism that class inheritance hierarchies exhibit, where a function that accepts an object of type Foo& can be passed any subtype of Foo, any class can be supplied as a template parameter so long as it supports all of the operations that the template uses. In the case of the function the requirement an argument must meet is clear (being a subtype of Foo), but in the case of a template the interface an object must meet is implicit in the implementation of that template. Concepts provide a mechanism for codifying the interface that a template parameter must meet.

The primary motivation of the introduction of concepts is to improve the quality of compiler error messages. If a programmer attempts to use a type that does not provide the interface a template requires, the compiler will generate an error. However, such errors are often difficult to understand, especially for novices. There are two main reasons for this. First, error messages are often displayed with template parameters spelled out in full; this leads to extremely large error messages. On some compilers, simple errors can generate several kilobytes of error messages. Second, they often do not immediately refer to the actual location of the error. For example, if the programmer tries to construct a vector of objects that do not have a copy constructor, the first error almost always refers to the code within the vector class itself that attempts to copy construct its contents; the programmer must be skilled enough to understand that the real error was that the type doesn't support everything the vector class requires.

In an attempt to resolve this issue, C++0x adds the language feature of concepts. Similar to how OOP uses a base-class to define restrictions on what a type can do, a concept is a named construct that specifies what a type must provide. Unlike OOP, however, the concept definition itself is not always associated explicitly with the type being passed into the template, but with the template definition itself:

template<LessThanComparable T>
  const T& min(const T &x, const T &y)
  {
    return y < x ? y : x;
  }

Rather than using an arbitrary class or typename for the template type parameter, it uses LessThanComparable, which is a concept that was previously defined. If a type passed into the min template function does not satisfy the requirements of the LessThanComparable concept, then a compile error will result, telling the user that the type used to instantiate the template does not fit the LessThanComparable concept.

A more generalized form of the concept is as follows:

template< typename T> requires LessThanComparable<T>
  const T& min(const T &x, const T &y)
  {
    return y < x ? y : x;
  }

The keyword requires begins a list of concept declarations. It can also be used for concepts that use multiple types. Additionally, it can be used as requires !LessThanComparable<T>, if the user wishes to prevent the use of this particular template if the type matches the concept. This mechanism can be used in a way similar to template specialization. A general template would handle types with fewer features, explicitly disallowing the use of other, more feature-rich, concepts. And those concepts would have their own specializations that use those particular features to achieve greater performance or some other functionality.

Concepts are defined as follows:

auto concept LessThanComparable< typename T>
{
  bool operator<(T, T);
}

The keyword auto, in this instance, means that any type that supports the operations specified in the concept will be considered to support the concept. Without the use of the auto keyword, then the type must use a concept map in order to declare itself as supporting the concept.

This concept says that any type that has an operator < that takes two objects of that type and returns a bool will be considered LessThanComparable. The operator need not be a free-function; it could be a member function of the type T.

Concepts can involve multiple objects as well. For example, concepts can express that a type is convertible from one type to another:

auto concept Convertible< typename T, typename U>
{
  operator U(const T&);
}

In order to use this in a template, it must use a generalized form of concept usage:

template< typename U, typename T> requires Convertible<T, U>
  U convert(const T& t)
  {
    return t;
  }

Concepts may be composed. For example, given a concept named Regular:

concept InputIterator< typename Iter, typename Value>
{
  requires Regular<Iter>;
  Value operator*(const Iter&);
  Iter& operator++(Iter&);
  Iter operator++(Iter&, int);
}

The first template parameter to the InputIterator concept must conform to the Regular concept.

Concepts can also be derived from one another, like inheritance. Like in class inheritance, types that meet the requirements of the derived concept also meet the requirements of the base concept. It is defined as per class derivation:

concept ForwardIterator< typename Iter, typename Value> : InputIterator<Iter, Value>
{
  //Add other requirements here.
}

Typenames can also be associated with a concept. These impose the requirement that, in templates that use those concepts, these typenames are available:

concept InputIterator< typename Iter>
{
  typename value_type;
  typename reference;
  typename pointer;
  typename difference_type;
  requires Regular<Iter>;
  requires Convertible<reference, value_type>;
  reference operator*(const Iter&); // dereference
  Iter& operator++(Iter&); // pre-increment
  Iter operator++(Iter&, int); // post-increment
  // ...
}

Concept maps allow types to be explicitly bound to a concept. They also allow types to, where possible, adopt the syntax of a concept without changing the definition of the type. As an example:

concept_map InputIterator<char*>
{
  typedef char value_type ;
  typedef char& reference ;
  typedef char* pointer ;
  typedef std::ptrdiff_t difference_type ;
};

This map fills in the required typenames for the InputIterator concept when applied to char* types.

As an added degree of flexibility, concept maps themselves can be templated. The above example can be extended to all pointer types:

template< typename T> concept_map InputIterator<T*>
{
  typedef T value_type ;
  typedef T& reference ;
  typedef T* pointer ;
  typedef std::ptrdiff_t difference_type ;
};

Further, concept maps can act as mini-types, with function definitions and other constructs commonly associated with classes:

concept Stack< typename X>
{
  typename value_type;
  void push(X&, const value_type&);
  void pop(X&);
  value_type top(const X&);
  bool empty(const X&);
};

template< typename T> concept_map Stack<std::vector<T> >
{
  typedef T value_type;
  void push(std::vector<T>& v, const T& x) { v.push_back(x); }
  void pop(std::vector<T>& v) { v.pop_back(); }
  T top(const std::vector<T>& v) { return v.back(); }
  bool empty(const std::vector<T>& v) { return v.empty(); }
};

This concept map allows templates that take types that implement the concept Stack to take a std::vector, remapping the function calls directly to the std::vector calls. Ultimately, this allows a pre-existing object to be converted, without touching the definition of the object, into an interface that a template function can utilize.

Finally, it should be noted that some requirements can be checked using static assertions. These can verify some requirements that templates need, but are really aimed at a different problem.

Object construction improvement

szerkesztés

A jelenlegi szabvány szerint a konstruktorok nem hívhatják az osztály másik konstruktorát. Nem lehet a bázisosztályok konstruktorát direkt levinni az osztályba, saját konstruktort kell definiálni, még ha meg is felelne a bázisosztályé a célnak. Nem konstans adattagokat nem lehet a deklarálásuk helyén inicializálni, csak a konstruktorban.

A C++0x megengedi, hogy másik konstruktort meghívjunk a konstruktorból, ezzel a kódmásolás szükségességét csökkentve. A konstruktorhívásnak az inicializációs listában kell szerepelnie:

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};

Ez kicsit megváltoztatja a szemantikáját az objektumok konstruálásának: az objektumot akkor tekinti tejesen megkonstruáltnak, ha bármelyik konstruktora lefutott. A leszármazott osztály konstruktora az összes bázisosztálybeli konstruktor után fut le.

Lehetőség lesz egy bázisosztály konstruktorainak öröklésére. Vagy az összeset átveszi egy bázisosztályból, vagy egyiket sem. A konstruktorok szignatúrájának különbözőnek kell lenniük, különösen oda kell figyelni erre többszörös öröklődésnél.

class BaseClass
{
public:
  BaseClass(int iValue);
};

class DerivedClass : public BaseClass
{
public:
  using BaseClass::BaseClass;  //BaseClass minden konstruktora (itt egy darab) átkerül ide:
};                             //hívható DerivedClass(0) néven

Az adattagok inicializálására a következő szintaxist vezeti be:

class SomeClass
{
public:
  SomeClass() {}
  explicit SomeClass(int iNewValue) : iValue(iNewValue) {}

private:
  int iValue = 5;
};

Ez az érték akkor kerül bele, ha az aktuálisan futó konstruktor nem inicializálja másképp, azaz a példában a default konstruktornál csak.

Használhatóak az összetettebb konstruktorhívási szintaxisok (int iValue(5);) is.

Null pointer konstans

szerkesztés

A C nyelv kezdete óta a 0 egész egyben a null pointer szerepét is betöltötte. A félreértések elkerülése érdekében bevezették a NULL makrót, ami általában 0-ra vagy ((void*)0)-ra helyettesítődött. Mivel C++-ban a void* nem konvertálódik automatikusan más pointer típusra, ezért a NULL általában a 0 egész konstansra értékelődik ki. Ez függvénytúlterhelés esetén félreértésekhez, nem várt viselkedéshez vezethet

void foo(char *);
void foo(int);

Ekkor foo(NULL); a foo(int) meghívását jelenti, ami nyílvánvalóan nem az, aminek első olvasatra látszik, megtévesztő.

A C++0x bevezeti a nullptr_t típusú nullptr konstanst, amely implicit konvertálódik tetszőleges pointer és tagra mutató pointer típusra, viszont egészekkel nem működik együtt.

char* pc = nullptr;     // OK
int * pi = nullptr;     // OK
int    i = nullptr;     // error

foo(nullptr);   // calls foo(char *), not foo(int);

A visszafele kompatibilitás miatt a 0 egész kifejezések konvertálódnak null pointer kifejezéssé.

Erősen típusos enumok

szerkesztés

In standard C++, enumerations are not type-safe. They are effectively integers, even when the enumeration types are distinct. This allows the comparison between two enum values of different enumeration types. The only safety that C++03 provides is that an integer or a value of one enum type does not convert implicitly to another enum type. Additionally, the underlying integral type, the size of the integer, cannot be explicitly specified; it is implementation defined. Lastly, enumeration values are scoped to the enclosing scope. As such, it is not possible for two separate enumerations to have matching member names.

C++0x will allow a special classification of enumeration that has none of these issues. This is expressed using the enum class declaration:

enum class Enumeration
{
  Val1,
  Val2,
  Val3 = 100,
  Val4 /* = 101 */
};

This enumeration is type-safe. Enum class values are not implicitly converted to integers; as such, they cannot be compared to integers either (the expression Enumeration::Val4 == 101 gives a compiler error).

The underlying type of enum classes is explicitly specified. The default, as in the above case, is int, but it can be changed as follows:

enum class Enum2 : unsigned int {Val1, Val2};

The scoping of the enumeration is also defined as the enumeration name's scope. Using the enumerator names requires explicitly scoping. Val1 is undefined, but Enum2::Val1 is defined.

Additionally, C++0x will allow standard enumerations to provide explicit scoping as well as the definition of the underlying type:

enum Enum3 : unsigned long {Val1 = 1, Val2};

The enumerator names are defined in the enumeration's scope (Enum3::Val1), but for backwards compatibility, enumerator names are also placed in the enclosing scope.

Forward declaration of enums is also possible in C++0x. Previously, the reason enum types could not be forward declared is because the size of the enumeration depends on its contents. As long as the size of the enumeration is specified by the application, it can be forward declared:

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.

Dupla kacsacsőr

szerkesztés

A jelenlegi szabvány a >> karaktersorozatot minden esetben a shift right operátorként értelmezi, ami templatek esetében majdnem mindig helytelen. Az új szabvány templatek típusparamétereinek feldolgozásakor precedenciát ad a > lezáró értelmezésének.

Explicit conversion operators

szerkesztés

Standard C++ added the explicit keyword as a modifier on constructors to prevent single-argument constructors to be used as implicit type conversion operators. However, this does nothing for actual conversion operators. For example, a smart pointer class may have an operator bool() to allow it to act more like a primitive pointer: if it includes this conversion, it can be tested with if(smart_ptr_variable) (which would be true if the pointer was non-null and false otherwise). However, this allows other, unintended conversions as well. Because C++ bool is defined as an arithmetic type, it can be implicitly converted to integral or even floating-point types, which allows for mathematical operations that are not intended by the user.

In C++0x, the explicit keyword can now be applied to conversion operators. As with constructors, it prevents the use of those conversion functions in implicit conversions. However, language contexts that specifically require a boolean value (the conditions of if-statements and loops, as well as operands to the logical operators) count as explicit conversions and can thus use a bool conversion operator.

Template typedefs

szerkesztés

In standard C++, it is only possible to define a template typedef if all of the template parameters are defined. It is not possible to create a typedef with undefined template parameters. For example:

template< typename first, typename second, int third>
class SomeType;

template< typename second>
typedef SomeType<OtherType, second, 5> TypedefName; //Illegal in C++

This will not compile.

C++0x will add this ability with the following syntax:

template< typename first, typename second, int third>
class SomeType;

template< typename second>
using TypedefName = SomeType<OtherType, second, 5>;

Korlátozatlan uniók

szerkesztés

A C++-ban erős megszorítások vannak arra nézve, milyen adatok lehetnek a részei egy únió adatszerkezetnek. Ezek nagy része teljesen feleslegesnek bizonyult, így az új szabvány minden megszorítást eltüntet a típusra vonatkozólag (a referencia típusok azonban továbbra sem megengedettek).

Egy egyszerű példa, amely bemutatja, milyen egyszerű adattípusok is ki voltak szorítva az unionból, de az új szabvány engedi őket:

struct point
{
  point() {}
  point(int x, int y): x_(x), y_(y) {}
  int x_, y_;
};
union
{
  int z;
  double w;
  point p;  // C++-ban szabálytalan: nem-triviális konstruktora van. C++0x-ben OK
};

A visszafele kompatibilitás nem sérül, mivel kizárólag bővítésről van szó.

Core language functionality improvements

szerkesztés

These features allow the language to do things in the language which were previously impossible, exceedingly verbose, or required non-portable libraries.

Variadic templates

szerkesztés

Prior to C++0x, templates (classes and functions) can only take a set number of arguments that have to be specified when a template is first declared. C++0x will allow template definitions to take an arbitrary number of arguments of any type.

template<typename... Values> class tuple;

This template class tuple will take any number of typenames as its template parameters:

class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> someInstanceName;

The number of arguments can be 0, so class tuple<> someInstanceName will work as well.

If one does not want to have a variadic template that takes 0 arguments, then this definition will work as well:

template<typename First, typename... Rest> class tuple;

Variadic templates may also apply to functions, thus not only providing a type-safe add-on to variadic functions (such as printf) - but also allowing a printf-like function to process non-trivial objects.

template<typename... Params> void printf(const std::string &strFormat, Params... parameters);

Note the use of the ... operator on the right of the type Params in the function signature, rather than the left as in the template specification. When the ... operator is on the left of the type, as in the template specification, it is the "pack" operator. This signifies that the type can be zero or more of them. When the operator is to the right of the type, it is the "unpack" operator. This causes the replication of the operations done on that type, one for each of the types that were packed with the pack operator. In the above example, the function printf will be given a parameter for each of the types packed in Params.

The use of variadic templates is often recursive. The variadic parameters themselves are not readily available to the implementation of a function or class. As such, the typical mechanism for defining something like a C++0x variadic printf replacement would be as follows:

void printf(const char *s)
{
  while (*s)
  {
    if (*s == '%' && *(++s) != '%')
      throw std::runtime_error("invalid format string: missing arguments");
    std::cout << *s++;
  }
}

template<typename T, typename... Args>
void printf(const char* s, T value, Args... args)
{
  while (*s)
  {
    if (*s == '%' && *(++s) != '%')
    {
      std::cout << value;
      printf(*s ? ++s : s, args...); // call even when *s == 0 to detect extra arguments
      return;
    }
    std::cout << *s++;
  }
  throw std::logic_error("extra arguments provided to printf");
}

This is a recursive call. Notice that the variadic template version of printf calls itself, or in the event that args is empty, calls the simple case.

There is no simple mechanism to iterate over the values of the variadic template. However, using the unpack operator, the template arguments can be unpacked virtually anywhere.

For example, a class can specify the following:

template <typename... BaseClasses> class ClassName : public BaseClasses...
{
public:

   ClassName (BaseClasses&&... baseClasses) : BaseClasses(baseClasses)... {}
}

The unpack operator will replicate the types for the base classes of ClassName, such that this class will be derived from each of the types passed in. Also, the constructor must take a reference to each base class, so as to initialize the base classes of ClassName.

With regard to function templates, the variadic parameters can be forwarded. When combined with r-value references (see above), this allows for perfect forwarding:

template<typename TypeToConstruct> struct SharedPtrAllocator
{
  template<typename ...Args> tr1::shared_ptr<TypeToConstruct> ConstructWithSharedPtr(Args&&... params)
  {
    return tr1::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
  }
}

This unpacks the argument list into the constructor of TypeToConstruct. The std::forward<Args>(params) syntax is the syntax that perfectly forwards arguments as their proper types, even with regard to rvalue-ness, to the constructor. The unpack operator will propagate the forwarding syntax to each parameter. This particular factory function automatically wraps the allocated memory in a tr1::shared_ptr for a degree of safety with regard to memory leaks.

Additionally, the number of arguments in a template parameter pack can be determined as follows:

template<typename ...Args> struct SomeStruct
{
  static const int size = sizeof...(Args);
}

The syntax SomeStruct<Type1, Type2>::size will be 2, while SomeStruct<>::size will be 0.

New string literals

szerkesztés

Standard C++ offers two kinds of string literals. The first kind, contained within double quotes, produces a null-terminated array of type const char. The second kind, defined as, L"", produces a null-terminated array of type const wchar_t, where wchar_t is a wide-character. Neither literal type offers support for Unicode-encoded string literals.

For the purpose of enhancing support for Unicode in C++ compilers, the definition of the type char has been modified to be both at least the size necessary to store an eight-bit coding of UTF-8 and large enough to contain any member of the compiler's basic execution character set. It was previously defined as only the latter.

There are three Unicode encodings that C++0x will support: UTF-8, UTF-16, and UTF-32. In addition to the previously noted changes to the definition of char, C++0x will add two new character types: char16_t and char32_t. Each of these is designed to store UTF-16 and UTF-32 respectively.

The following shows how to create string literals for each of these encodings:

u8"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."

The type of the first string is the usual const char[]. The type of the second string is const char16_t[]. The type of the third string is const char32_t[].

When building Unicode string literals, it is often useful to insert Unicode codepoints directly into the string. To do this, C++0x will allow the following syntax:

u8"This is a Unicode Character: \u2018."
u"This is a bigger Unicode Character: \u2018."
U"This is a Unicode Character: \u2018."

The number after the '\u' is a hexadecimal number; it does not need the usual '0x' prefix. The identifier '\u' represents a 16-bit Unicode codepoint; to enter a 32-bit codepoint, use '\U' and a 32-bit hexadecimal number. Only valid Unicode codepoints can be entered. For example, codepoints on the range U+D800—U+DFFF are forbidden, as they are reserved for surrogate pairs in UTF-16 encodings.

It is also sometimes useful to avoid escaping strings manually, particularly for using literals of XML files or scripting languages. C++0x will provide a raw string literal:

R"[The String Data \ Stuff " ]"
R"delimiter[The String Data \ Stuff " ]delimiter"

In the first case, everything between the [ ] brackets is part of the string. The " and \ characters do not need to be escaped. In the second case, the "delimiter[ starts the string, and it only ends when ]delimiter" is reached. The string delimiter can be any arbitrary string, which allows the user to use ] characters within raw string literals.

Raw string literals can be combined with the wide literal or any of the Unicode literals:

u8R"XXX[I'm a "raw UTF-8" string.]XXX"
uR"*@[This is a "raw UTF-16" string.]*@"
UR"[This is a "raw UTF-32" string.]"

User-defined literals

szerkesztés

Standard C++ provides a number of literals. The characters, "12.5" are a literal that is resolved by the compiler as a type double with the value of 12.5. However, the addition of the suffix "f", as in "12.5f", creates a value of type float that contains the value 12.5. The suffix modifiers for literals is fixed by the C++ specification, and C++ code cannot create new literal modifiers.

C++0x will also include the ability for the user to define new kinds of literal modifiers that will construct objects based on the string of characters that the literal modifies.

Literals transformation is redefined into two distinct phases: raw and cooked. A raw literal is a sequence of characters of some specific type, while the cooked literal is of a separate type. The C++ literal 1234, as a raw literal, is this sequence of characters '1', '2', '3', '4'. As a cooked literal, it is the integer 1234. The C++ literal 0xA in raw form is '0', 'x', 'A', while in cooked form it is the integer 10.

Literals can be extended in both raw and cooked forms, with the exception of string literals, which can only be processed in cooked form. This exception is due to the fact that strings have prefixes that affect the specific meaning and type of the characters in question.

All user-defined literals are suffixes; defining prefix literals is not possible. Suffixes that do not start with an underscore are reserved by the language for future use.

User-defined literals processing the raw form of the literal are defined as follows:

OutputType operator "" _Suffix(const char *literal_string);

OutputType someVariable = "1234"_Suffix;

The second statement executes the code defined by the user-defined literal function. This function is passed "1234" as a C-style string, so it has a null terminator.

An alternative mechanism for processing raw literals is through a variadic template:

template<char...> OutputType operator "" _Suffix();

OutputType someVariable = "1234"_Suffix;

This instantiates the literal processing function as operator""_Suffix<'1', '2', '3', '4'>. In this form, there is no terminating null character to the string. The main purpose to doing this is to use C++0x's constexpr keyword and the compiler to allow the literal to be transformed entirely at compile time, assuming OutputType is a constexpr-constructable and copyable type, and the literal processing function is a constexpr function.

For cooked literals, the type of the cooked literal is used, and there is no alternative template form:

OutputType operator "" _Suffix(int the_value);

OutputType someVariable = "1234"_Suffix;

For string literals, the following are used, in accordance with the previously mentioned new string prefixes:

OutputType operator "" _Suffix(const char * string_values, size_t num_chars);
OutputType operator "" _Suffix(const wchar_t * string_values, size_t num_chars);
OutputType operator "" _Suffix(const char16_t * string_values, size_t num_chars);
OutputType operator "" _Suffix(const char32_t * string_values, size_t num_chars);

OutputType someVariable = "1234"_Suffix;      //Calls the const char * version
OutputType someVariable = u8"1234"_Suffix;    //Calls the const char * version
OutputType someVariable = L"1234"_Suffix;     //Calls the const wchar_t * version
OutputType someVariable = u"1234"_Suffix;     //Calls the const char16_t * version
OutputType someVariable = U"1234"_Suffix;     //Calls the const char32_t * version

Character literals are defined similarly.

Multitasking memory model

szerkesztés

The C++ standard committee plans to standardise support for multithreaded programming.

There are two parts involved: defining a memory model which will allow multiple threads to co-exist in a program, and defining support for interaction between threads. The second part will be provided via library facilities, see threading facilities.

A memory model is necessary in order to dictate under which circumstances multiple threads may access the same memory location. A program which adheres to the rules is guaranteed to execute correctly, but a program which breaks the rules may have unexpected behavior due to compiler optimizations and problems with memory coherence.

Thread-local storage

szerkesztés

In a multi-threaded environment, it is common for every thread to have some unique variables. This already happens for the local variables of a function, but it does not happen for global and static variables.

A new thread-local storage duration (in addition to the existing static, dynamic and automatic) has been proposed for the next standard. Thread local storage will be indicated by the storage specifier thread_local.

Any object which could have static storage duration (i.e. lifetime spanning the entire execution of the program) may be given thread-local duration instead. The intent is that like any other static-duration variable, a thread-local object can be initialized using a constructor and destroyed using a destructor.

Defaulting/deleting of standard functions on C++ objects

szerkesztés

In standard C++, the compiler will provide, for objects that do not provide for themselves, a default constructor, a copy constructor, a copy assignment operator operator=, and a destructor. As mentioned, the user can override these defaults by defining their own version. C++ also defines several global operators (such as operator=, and operator new) that work on all classes, which the user can override.

The problem is that there are very few controls over the creation of these defaults. Making a class inherently non-copyable, for example, requires declaring a private copy constructor and copy assignment operator and not defining them. Attempting to use these functions will cause a compiler or linker error. However, this is not an ideal solution.

Further, in the case of the default constructor, it is useful to want to explicitly tell the compiler to generate it. The compiler will not generate a default constructor if the object is defined with any constructors. This is useful in many cases, but it is also useful to be able to have both a specialized constructor and the compiler-generated default.

C++0x will allow the explicit use, or disuse, of these standard object functions. For example, the following type explicitly declares that it is using the default constructor:

struct SomeType
{
  SomeType() = default; //The default constructor is explicitly stated.
  SomeType(OtherType value);
};

Alternatively, certain features can be explicitly disabled. For example, the following type is non-copyable:

struct NonCopyable
{
  NonCopyable & operator=(const NonCopyable&) = delete;
  NonCopyable(const NonCopyable&) = delete;
  NonCopyable() = default;
};

A type can be made impossible to allocate with operator new:

struct NonNewable
{
  void *operator new(std::size_t) = delete;
};

This object can only ever be allocated as a stack object or as a member of another type. It cannot be directly heap allocated without non-portable trickery. (Since placement new is the only way to call a constructor on user-allocated memory and this use has been forbidden as above, the object cannot be properly constructed.)

The = delete specifier can be used to prohibit calling any function, which can be used to disallow calling a member function with particular parameters. For example:

struct NoDouble
{
  void f(int i);
  void f(double) = delete;
};

An attempt to call f() with a double will be rejected by the compiler, instead of performing a silent conversion to int. This can be generalized to disallow calling the function with any type other than int as follows:

struct OnlyInt
{
  void f(int i);
  template<class T> void f(T) = delete;
};

Type long long int

szerkesztés

On 32-bit systems, a long long int is an integer type that has at least 64 useful bits. C99 introduced this type to standard C and it is supported as an extension by most C++ compilers. (Indeed, some compilers supported it before its introduction to C99.)[forrás?] C++0x will add this type to standard C++.

Static assertions

szerkesztés

The C++ standard provides two methods to test assertions: the macro assert and the preprocessor directive #error. However, neither is appropriate for use in templates: the macro tests the assertion at execution-time, while the preprocessor directive tests the assertion during preprocessing, which happens before instantiation of templates. Neither is appropriate for testing properties that are dependent on template parameters.

The new utility introduces a new way to test assertions at compile-time, using the new keyword static_assert. The declaration assumes the following form:

static_assert( constant-expression, error-message ) ;

Here are some examples of how static_assert can be used:

static_assert( 3.14 < GREEKPI && GREEKPI < 3.15, "GREEKPI is inaccurate!" ) ;
template< class T >
struct Check
{
  static_assert( sizeof(int) <= sizeof(T), "T is not big enough!" ) ;
} ;

When the constant expression is false the compiler produces an error message. The first example represents an alternative to the preprocessor directive #error, in contrast in the second example the assertion is checked at every instantiation of the template class Check.

Static assertions are useful outside of templates as well. For instance, a particular implementation of an algorithm might depend on the size of a long long being larger than an int, something the standard does not guarantee. Such an assumption is valid on most systems and compilers, but not all.

Allow 'sizeof' to work on members of classes without an explicit object

szerkesztés

In standard C++, the sizeof operation can be used on types and objects. But it cannot be used to do the following:

struct SomeType { OtherType member; };

sizeof(SomeType::member); //Does not work with C++03. Okay with C++0x

This should return the size of OtherType. C++03 does not allow this, so it is a compile error. C++0x will allow it.

C++ standard library changes

szerkesztés

A number of new features will be introduced in the C++0x standard library. Many of these can be implemented under the current standard, but some rely (to a greater or lesser extent) on new C++0x core features.

A large part of the new libraries are defined in the document C++ Standards Committee's Library Technical Report (called TR1), which was published in 2005. Various full and partial implementations of TR1 are currently available using the namespace std::tr1. For C++0x they will be moved to namespace std. However, as TR1 features are brought into the C++0x standard library, they are upgraded where appropriate with C++0x language features that were not available in the initial TR1 version. Also, they may be enhanced with features that were possible under C++03, but were not part of the original TR1 specification.

The committee intends to create a second technical report (called TR2) after the standardization of C++0x is complete. Library proposals which are not ready in time for C++0x will be put into TR2 or further technical reports.

The following proposals are under way for C++0x.

Upgrades to standard library components

szerkesztés

C++0x offers a number of new language features that the currently existing standard library components can benefit from. For example, most standard library containers can benefit from Rvalue reference based move constructor support, both for quickly moving heavy containers around and for moving the contents of those containers to new memory locations. The standard library components will be upgraded with new C++0x language features where appropriate. These include, but are not necessarily limited to:

  • Concepts
  • Rvalue references and the associated move support
  • Support for the UTF-16 and UTF-32 character types
  • Variadic templates (coupled with Rvalue references to allow for perfect forwarding)
  • Compile-time constant expressions
  • Decltype
  • Explicit conversion operators
  • Default/Deleted functions

Additionally, much time has passed since C++ was standardized. A great deal of code using the standard library has been written; this has revealed portions of the standard libraries that could use some improvement. Among the many areas of improvement being considered are standard library allocators. A new scope-based model of allocators will be included in the C++0x to supplement the current model.

Threading facilities

szerkesztés

While the C++0x language will provide a memory model that supports threading, the primary support for actually using threading will come with the C++0x standard library.

A thread class (std::thread) will be provided which will take a function object — and an optional series of arguments to pass to it — to run in the new thread. It will be possible to cause a thread to halt until another executing thread completes, providing thread joining support through the std::thread::join() member function. Access will also be provided, where feasible, to the underlying native thread object(s) for platform specific operations by the std::thread::native_handle() member function.

For synchronization between threads, appropriate mutexes (std::mutex, std::recursive_mutex, etc.) and condition variables (std::condition_variable and std::condition_variable_any) will be added to the library. This will be accessible through RAII locks (std::lock_guard and std::unique_lock) and locking algorithms for easy use.

For high-performance low-level work it is sometimes necessary to communicate between threads without the overhead of mutexes. This is achieved using atomic operations on memory locations, together with appropriate memory barriers. An atomics library will be provided which will allow specifying the minimum synchronization necessary for an operation.

The C++0x thread library will also include futures for passing asynchronous results between threads, and std::packaged_task for wrapping up a function call that can generate such an asynchronous result.

Further high-level threading facilities such as thread pools have been remanded to a future C++ technical report. They will not be a part of C++0x, but their eventual implementation is expected to be built entirely on top of the thread library features.

Tuple types

szerkesztés

Tuples are collections composed of heterogeneous objects of pre-arranged dimensions. A tuple can be considered a generalization of a struct's member variables.

The C++0x version of the TR1 tuple type will benefit from C++0x features like variadic templates. The TR1 version required an implementation-defined maximum number of contained types, and required substantial macro trickery to implement reasonably. By contrast, the implementation of the C++0x version requires no explicit implementation-defined maximum number of types. Though compilers will almost certainly have an internal maximum recursion depth for template instantiation (which is normal), the C++0x version of tuples will not expose this value to the user.

Using variadic templates, the declaration of the tuple class looks as follows:

template <class ...Types> class tuple;

An example of definition and use of the tuple type:

typedef tuple< int, double, long &, const char * > test_tuple ;
long lengthy = 12 ;
test_tuple proof( 18, 6.5, lengthy, "Ciao!" ) ;
lengthy = get<0>(proof) ;  // Assign to 'lengthy' the value 18.
get<3>(proof) = " Beautiful!" ;  // Modify the tuple’s fourth element.

It’s possible to create the tuple proof without defining its contents, but only if the tuple elements' types possess default constructors. Moreover, it’s possible to assign a tuple to another tuple: if the two tuples’ types are the same, it is necessary that each element type possesses a copy constructor; otherwise, it is necessary that each element type of the right-side tuple is convertible to that of the corresponding element type of the left-side tuple or that the corresponding element type of the left-side tuple has a suitable constructor.

typedef tuple< int , double, string       > tuple_1 t1 ;
typedef tuple< char, short , const char * > tuple_2 t2( 'X', 2, "Hola!" ) ;
t1 = t2 ;  // Ok, first two elements can be converted,
           // the third one can be constructed from a 'const char *'.

Relational operators are available (among tuples with the same number of elements), and two expressions are available to check a tuple’s characteristics (only during compilation):

  • tuple_size<T>::value returns the elements’ number of the tuple T,
  • tuple_element<I, T>::type returns the type of the object number I of the tuple T.

Hash tables

szerkesztés

Including hash tables (unordered associative containers) in the C++ standard library is one of the most recurring requests. It was not adopted in the current standard due to time constraints only. Although this solution is less efficient than a balanced tree in the worst case (in the presence of many collisions), it performs better in many real applications.

Collisions will be managed only through linear chaining because the committee doesn’t consider opportune to standardize solutions of open addressing that introduce quite a lot of intrinsic problems (above all when erasure of elements is admitted). To avoid name clashes with non-standard libraries that developed their own hash table implementations, the prefix “unordered” will be used instead of “hash”.

The new utility will have four types of hash tables, differentiated by whether or not they accept elements with the same key (unique keys or equivalent keys), and whether they map each key to an associated value.

Type of hash table Arbitrary mapped type Equivalent keys
unordered_set
unordered_multiset
unordered_map
unordered_multimap

New classes fulfill all the requirements of a container class, and have all the methods necessary to access elements: insert, erase, begin, end.

This new utility doesn’t need any C++ language core extensions (though the implementation will take advantage of various C++0x language features), only a small extension of the header <functional> and the introduction of headers <unordered_set> and <unordered_map>. No other changes to any existing standard classes are needed, and it doesn’t depend on any other extensions of the standard library.

Although it's being considered that if the "concepts" feature is ready and working, the existing "map" and "set" symbols will be reused, as there's no point in separate "unordered" containers. To create a "hash map", for example, it will be enough to declare it as std::map<std::string, int, std::hash>, which would be the same as the hash_map extension: hash_map<std::string, int>, where third parameter defaults to "hash".

Regular expressions

szerkesztés

Many more or less standardized libraries were created to manage regular expressions. Since the use of these algorithms is very common, the standard library will include them using all potentialities of an object oriented language.

The new library, defined in the new header <regex>, is made of a couple of new classes:

  • regular expressions are represented by instance of the template class basic_regex;
  • occurrences are represented by instance of the template class match_results.

The function regex_search is used for searching, while for ‘search and replace’ the function regex_replace is used which returns a new string. The algorithms regex_search and regex_replace take a regular expression and a string and write the occurrences found in the struct match_results.

Here is an example on the use of match_results:

const char *reg_esp = "[ ,.\\t\\n;:]" ;  // List of separator characters.

regex rgx(reg_esp) ;  // 'regex' is an instance of the template class
                      // 'basic_regex' with argument of type 'char'.
cmatch match ;  // 'cmatch' is an instance of the template class
                // 'match_results' with argument of type 'const char *'.
const char *target = "Polytechnic University of Turin " ;

// Identifies all words of 'target' separated by characters of 'reg_esp'.
if( regex_search( target, match, rgx ) )
{
  // If words separated by specified characters are present.

  const size_t n = match.size();
  for( size_t a = 0 ; a < n ; a++ )
  {
    string str( match[a].first, match[a].second ) ;
    cout << str << "\n" ;
  }
}

Note the use of double backslashes, because the C++ preprocessor uses backslash as an escape character. The C++0x raw string feature could be used to avoid the problem.

The library “regex” doesn’t need alteration of any existing header (though it will use them where appropriate) and no extension of the core language.

General-purpose smart pointers

szerkesztés

These pointers are taken from the TR1 smart pointers.

The shared_ptr is a reference-counted pointer that acts as much as possible like a regular C++ data pointer. The TR1 implementation lacked certain pointer features such as aliasing and pointer arithmetic, but the C++0x version will add these.

The shared pointer will automatically destroy its contents only when there are no shared pointers referencing the object originally created for the shared pointer.

A weak_ptr is a reference to an object referenced by a shared_ptr that can determine if that object has been deleted or not. weak_ptr itself cannot be dereferenced; accessing the actual pointer requires the creation of a shared_ptr object. This can be done in one of two ways. The shared_ptr class has a constructor that takes a weak_ptr and the weak_ptr class has a lock member function that returns a shared_ptr. The weak_ptr does not own the object it references, and thus the existence of a weak_ptr will not prevent the deletion of the object.

Here it is an example of use of shared_ptr:

int main( )
{
  shared_ptr<double> p_first(new double) ;

  {
    shared_ptr<double> p_copy = p_first ;

    *p_copy = 21.2;

  }  // Destruction of 'p_copy' but not of the allocated double.

  return 0;  // Destruction of 'p_first' and accordingly of the allocated double.
}

unique_ptr will be provided as a replacement for auto_ptr which will be deprecated. It provides all the features of auto_ptr with the exception of unsafe implicit moving from lvalues. Unlike auto_ptr, unique_ptr can be used with the C++0x move-aware containers.

Extensible random number facility

szerkesztés

The C standard library provides the ability to generate pseudorandom numbers through the function rand. However, the algorithm is delegated entirely to the library vendor. C++ inherited this functionality with no changes, but C++0x will provide a new method for generating pseudorandom numbers.

C++0x's random number functionality is split into two parts: a generator engine that contains the random number generator's state and produces the pseudorandom numbers; and a distribution, which determines the range and mathematical distribution of the outcome. These two are combined to form a random number generator object.

Unlike the C standard rand, the C++0x mechanism will come with three generator engine algorithms, each with its own strengths and weaknesses:

template class int/float quality speed size of state*
linear_congruential int medium medium 1
subtract_with_carry both medium fast 25
mersenne_twister int good fast 624

C++0x will also provide a number of standard distributions: uniform_int, bernoulli_distribution, geometric_distribution, poisson_distribution, binomial_distribution, uniform_real, exponential_distribution, normal_distribution, and gamma_distribution.

The generator and distributions are combined as in the following example:

std::uniform_int<int> distribution( 0, 99 );
std::mt19937 engine;
std::variate_generator<mt19937, uniform_int<int>> generator( engine, distribution );
int random = generator();  // Assign a value among 0 and 99.

The interface used by std::variate_generator is well defined; the user can create both generator engines and distribution objects to use with this class.

Wrapper reference

szerkesztés

A wrapper reference is obtained from an instance of the template class reference_wrapper. Wrapper references are similar to normal references (‘&’) of the C++ language. To obtain a wrapper reference from any object the template class ref is used (for a constant reference cref is used).

Wrapper references are useful above all for template functions, when we need to obtain references to parameters rather than copies:

// This function will obtain a reference to the parameter 'r' and increase it.
void f( int &r )  { r++ ; }

// Template function.
template< class F, class P > void g( F f, P t )  { f(t) ; }

int main()
{
  int i = 0 ;
  g( f, i ) ;  // 'g<void ( int &r ), int>' is instantiated
               // then 'i' will not be modified.
  cout << i << endl ;  // Output -> 0

  g( f, ref(i) ) ;  // 'g<void(int &r),reference_wrapper<int>>' is instanced
                    // then 'i' will be modified.
  cout << i << endl ;  // Output -> 1
}

This new utility will be added to the existing <utility> header and doesn’t need further extensions of the C++ language.

Polymorphous wrappers for function objects

szerkesztés

Polymorphous wrappers for function objects (also called “polymorphic function object wrappers”) are similar to function pointers in semantics and syntax, but are less tightly bound and can indiscriminately refer to anything which can be called (function pointers, member function pointers, or functors) whose arguments are compatible with those of the wrapper.

Through the next example it is possible to understand its characteristics:

function<int ( int, int )> pF ;  // Wrapper creation using
                                 // template class 'function'.

plus<int> add ;  // 'plus' is declared as 'template<class T> T plus( T, T ) ;'
                 // then 'add' is type 'int add( int x, int y )'.
 
pF = &add ;  // OK - Parameters and return types are the same.
 
int a = pF( 1, 2 ) ;  // NOTE: if the wrapper 'pF' does not refer to any function,
                      // the exception 'std::bad_function_call' is thrown.

function<bool ( short, short )> pG ;
if( !pG )  // Always true because 'pG' has not yet
           // been assigned a function.
{
  bool adjacent( long x, long y ) ;
  pG = &adjacent ;  // OK - Parameters and return types are convertible.
  
  struct test
  {
    bool operator()( short x, short y ) ;
  } car ;
  pG = ref(car) ;  // 'ref' is a template function that returns the wrapper
                   // of member function 'operator()' of struct 'car'.
}
pF = pG ;  // OK - Parameters and return types are convertible.

The template class function will be defined inside the header <functional>, and doesn't require any changes to the C++ language.

Type traits for metaprogramming

szerkesztés

Metaprogramming consists of creating a program that creates or modifies another program (or itself). This can happen during compilation or during execution. The C++ Standards Committee has decided to introduce a library that allows metaprogramming during compilation through templates.

Here is an example of what is possible, using the actual standard, through metaprogramming: a recursion of template instances for exponential calculus.

template< int B, int N >
struct Pow
{
  // recursive call and recombination.
  enum{ value = B*Pow< B, N-1 >::value } ;
} ;
template< int B > struct Pow< B, 0 >  // ''N == 0'' condition of termination.
{
  enum{ value = 1 } ;
} ;
int quartic_of_three = Pow< 3, 4 >::value ;

Many algorithms can operate on different types of data; C++'s templates support generic programming and make code more compact and useful. Nevertheless it is common for algorithms to need information on the data types being used. This information can be extracted during instantiation of a template class using type traits.

Type traits can identify the category of an object and all the characteristic of a class (or of a struct). They are defined in the new header <type_traits>.

In the next example there is the template function ‘elaborate’ that, depending on the given data types, will instantiate one of the two proposed algorithms (algorithm.do_it).

// First way of operating.
template< bool B > struct algorithm
{
  template< class T1, class T2 > int do_it( T1 &, T2 & )  { /*...*/ }
} ;
// Second way of operating.
template<> struct algorithm<true>
{
  template< class T1, class T2 > int do_it( T1, T2 )  { /*...*/ }
} ;

// Instantiating 'elaborate' will automatically instantiate the correct way to operate.
template< class T1, class T2 > int elaborate( T1 A, T2 B )
{
  // Use the second way only if 'T1' is an integer and if 'T2' is
  // in floating point, otherwise use the first way.
  return algorithm< is_integral<T1>::value && is_floating_point<T2>::value >::do_it( A, B ) ;
}

Through type traits, defined in header <type_transform>, it’s also possible to create type transformation operations (static_cast and const_cast are insufficient inside a template).

This type of programming produces elegant and concise code; however the weak point of these techniques is the debugging: uncomfortable during compilation and very difficult during program execution.

Uniform method for computing return type of function objects

szerkesztés

Determining the return type of a template function object at compile-time is not intuitive, particularly if the return value depends on the parameters of the function. As an example:

struct clear
{
  int    operator()( int    ) ;  // The parameter type is
  double operator()( double ) ;  // equal to the return type.
} ;

template< class Obj > class calculus
{
  public:
    template< class Arg > Arg operator()( Arg& a ) const
    {
      return member(a) ;
    }
  private:
    Obj member ;
} ;

Instantiating the class template calculus<clear>, the function object of calculus will have always the same return type as the function object of clear. However, given class confused below:

struct confused
{
  double operator()( int    ) ;  // The parameter type is NOT
  int    operator()( double ) ;  // equal to the return type.
} ;

Attempting to instantiate calculus<confused> will cause the return type of calculus to not be the same as that of class confused. The compiler may generate warnings about the conversion from int to double and vice-versa.

TR1 introduces, and C++0x adopts, the template class std::result_of that allows to determine and use the return type of a function object for every declaration. The object calculus_ver2 uses the std::result_of object to derive the return type of the function object:

template< class Obj >
class calculus_ver2
{
  public:
    template< class Arg >
    typename std::result_of<Obj(Arg)>::type operator()( Arg& a ) const
    { 
      return member(a) ;
    }
  private:
    Obj member ;
} ;

In this way in instances of function object of calculus_ver2<confused> there are no conversions, warnings, or errors.

The only change from the TR1 version of std::result_of is that the TR1 version allowed an implementation to fail to be able to determine the result type of a function call. Due to changes to C++ for supporting decltype, the C++0x version of std::result_of no longer needs these special cases; implementations are required to compute a type in all cases.

References

szerkesztés

C++ Standards Committee papers

szerkesztés
szerkesztés

[[Category:C++]] [[Category:Computer and telecommunication standards]] [[Category:Articles with example C++ code]]