Overloading Bracket Operator C++ Assignment Answers



Chapter 11: More Operator Overloading

Having covered the overloaded assignment operator in chapter 9, and having shown several examples of other overloaded operators as well (i.e., the insertion and extraction operators in chapters 3 and 6), we now take a look at operator overloading in general.

11.1: Overloading `operator[]()'

As our next example of operator overloading, we introduce a class encapsulating an array of s. Indexing the array elements is possible using the standard array index operator , but additionally checks for array bounds overflow are performed. Furthermore, the index operator () is interesting in that it can be used in expressions as both lvalue and as rvalue.

Here is an example showing the basic use of the class:

int main() { IntArray x{ 20 }; // 20 ints for (int i = 0; i < 20; i++) x[i] = i * 2; // assign the elements for (int i = 0; i <= 20; i++) // produces boundary overflow cout << "At index " << i << ": value is " << x[i] << '\n'; }

First, the constructor is used to create an object containing 20 s. The elements stored in the object can be assigned or retrieved. The first -loop assigns values to the elements using the index operator, the second -loop retrieves the values but also results in a run-time error once the non-existing value is addressed. The class interface is:

#include <cstddef> class IntArray { int *d_data; size_t d_size; public: IntArray(size_t size = 1); IntArray(IntArray const &other); ~IntArray(); IntArray &operator=(IntArray const &other); // overloaded index operators: int &operator[](size_t index); // first int operator[](size_t index) const; // second void swap(IntArray &other); // trivial private: void boundary(size_t index) const; int &operatorIndex(size_t index) const; }; This class has the following characteristics:
  • One of its constructors has a parameter having a default argument value, specifying the number of elements in the object.
  • The class internally uses a pointer to reach allocated memory. Hence, the necessary tools are provided: a copy constructor, an overloaded assignment operator and a destructor.
  • Note that there are two overloaded index operators. Why are there two?

    The first overloaded index operator allows us to reach and modify the elements of non-constant objects. This overloaded operator's prototype is a function returning a reference to an . This allows us to use expressions like as rvalues or lvalues.

    With non-const objects can therefore be used to retrieve and to assign values. The return value of the non-const member is not an , but an . In this situation we don't use , as we must be able to modify the element we want to access when the operator is used as lvalue.

    This whole scheme fails if there's nothing to assign. Consider the situation where we have an . Such an object is an immutable const object. The compiler detects this and refuses to compile this object definition if only the non-const is available. Hence the second overloaded index operator is added to the class's interface. Here the return value is an , rather than an , and the member function itself is a member function. This second form of the overloaded index operator is automatically used by the compiler with objects. It is used for value retrieval instead of value assignment. That, of course, is precisely what we want when using objects. In this situation members are overloaded only by their attribute. This form of function overloading was introduced earlier in the C++ Annotations (sections 2.5.4 and 7.7).

    Note the difference between the return types of the two overloaded index operators: and . Here, using a value return type for the const-index operator is OK, because the return type is a simple built-in type: returning a copy of the requested value is likely more efficient than returning a const reference, which must be dereferenced whenever it is used. However, for more complex return types (generally: objects) value return types should be avoided and the const index operator should define a return type instead of a value return type.

    Since stores values of a primitive type 's could also have defined a value return type. However, with objects one usually doesn't want the extra copying that's implied with value return types. In those cases return values are preferred for member functions. So, in the class an return value could have been used as well, resulting in the following prototype:

    int IntArray::operator[](size_t index) const;
  • As there is only one pointer data member, the destruction of the memory allocated by the object is a simple .
Now, the implementation of the members (omitting the trivial implementation of , cf. chapter 9) are: #include "intarray.ih" IntArray::IntArray(size_t size) : d_size(size) { if (d_size < 1) throw string("IntArray: size of array must be >= 1"); d_data = new int[d_size]; } IntArray::IntArray(IntArray const &other) : d_size(other.d_size), d_data(new int[d_size]) { memcpy(d_data, other.d_data, d_size * sizeof(int)); } IntArray::~IntArray() { delete[] d_data; } IntArray &IntArray::operator=(IntArray const &other) { IntArray tmp(other); swap(tmp); return *this; } int &IntArray::operatorIndex(size_t index) const { boundary(index); return d_data[index]; } int &IntArray::operator[](size_t index) { return operatorIndex(index); } int IntArray::operator[](size_t index) const { return operatorIndex(index); } void IntArray::boundary(size_t index) const { if (index < d_size) return; ostringstream out; out << "IntArray: boundary overflow, index = " << index << ", should be < " << d_size << '\n'; throw out.str(); } Note how the members were implemented: as non-const members may call const member functions and as the implementation of the member function is identical to the non-const member function's implementation both members could be defined inline using an auxiliary function . A member function may return a non-const reference (or pointer) return value, referring to one of the data members of its object. Of course, this is a potentially dangerous backdoor that may break data hiding. However, the members in the public interface prevent this breach and so the two public members may themselves safely call the same member, that defines a private backdoor.

11.2: Overloading the insertion and extraction operators

Classes may be adapted in such a way that their objects may be inserted into and extracted from, respectively, a and .

The class defines insertion operators for primitive types, such as , , etc.. In this section we learn how to extend the existing functionality of classes (in particular and ) in such a way that they can be used also in combination with classes developed much later in history.

In particular we will show how the insertion operator can be overloaded allowing the insertion of any type of object, say (see chapter 9), into an . Having defined such an overloaded operator we're able to use the following code:

Person kr("Kernighan and Ritchie", "unknown", "unknown"); cout << "Name, address and phone number of Person kr:\n" << kr << '\n'; The statement << uses <<. This member function has two operands: an and a . The required action is defined in an overloaded free function<< expecting two arguments: // declared in `person.h' std::ostream &operator<<(std::ostream &out, Person const &person); // defined in some source file ostream &operator<<(ostream &out, Person const &person) { return out << "Name: " << person.name() << ", " "Address: " << person.address() << ", " "Phone: " << person.phone(); } The free function << has the following noteworthy characteristics:
  • The function returns a reference to an object, to enable `chaining' of the insertion operator.
  • The two operands of << are passed to the free function as its arguments. In the example, the parameter was initialized by , the parameter by .

In order to overload the extraction operator for, e.g., the class, members are needed modifying the class's private data members. Such modifiers are normally offered by the class interface. For the class these members could be the following:

void setName(char const *name); void setAddress(char const *address); void setPhone(char const *phone); These members may easily be implemented: the memory pointed to by the corresponding data member must be deleted, and the data member should point to a copy of the text pointed to by the parameter. E.g., void Person::setAddress(char const *address) { delete[] d_address; d_address = strdupnew(address); } A more elaborate function should check the reasonableness of the new address ( also shouldn't be a 0-pointer). This however, is not further pursued here. Instead, let's have a look at the final >>. A simple implementation is: istream &operator>>(istream &in, Person &person) { string name; string address; string phone; if (in >> name >> address >> phone) // extract three strings { person.setName(name.c_str()); person.setAddress(address.c_str()); person.setPhone(phone.c_str()); } return in; } Note the stepwise approach that is followed here. First, the required information is extracted using available extraction operators. Then, if that succeeds, modifiers are used to modify the data members of the object to be extracted. Finally, the stream object itself is returned as a reference.

11.3: Conversion operators

A class may be constructed around a built-in type. E.g., a class , constructed around the type. Such a class may define all kinds of operations, like assignments. Take a look at the following class interface, designed after the class: class String { char *d_string; public: String(); String(char const *arg); ~String(); String(String const &other); String &operator=(String const &rvalue); String &operator=(char const *rvalue); }; Objects of this class can be initialized from a , and also from a itself. There is an overloaded assignment operator, allowing the assignment from a object and from a  (Note that the assignment from a also allows the null-pointer. An assignment like is perfectly in order.).

Usually, in classes that are less directly linked to their data than this class, there will be an accessor member function, like a member . However, the need to use this latter member doesn't appeal to our intuition when an array of objects is defined by, e.g., a class . If this latter class provides the to access individual members, it would most likely offer at least the following class interface:

class StringArray { String *d_store; size_t d_n; public: StringArray(size_t size); StringArray(StringArray const &other); StringArray &operator=(StringArray const &rvalue); ~StringArray(); String &operator[](size_t index); }; This interface allows us to assign elements to each other: StringArray sa{ 10 }; sa[4] = sa[3]; // String to String assignment But it is also possible to assign a to an element of : sa[3] = "hello world"; Here, the following steps are taken:
  • First, is evaluated. This results in a reference.
  • Next, the class is inspected for an overloaded assignment, expecting a to its right-hand side. This operator is found, and the string object receives its new value.
Now we try to do it the other way around: how to access the that's stored in ? The following attempt fails: char const *cp = sa[3]; It fails since we would need an overloaded assignment operator for the 'class' . Unfortunately, there isn't such a class, and therefore we can't build that overloaded assignment operator (see also section 11.14). Furthermore, casting won't work as the compiler doesn't know how to cast a to a . How to proceed?

One possibility is to define an accessor member function :

char const *cp = sa[3].c_str() This compiles fine but looks clumsy.... A far better approach would be to use a conversion operator.

A conversion operator is a kind of overloaded operator, but this time the overloading is used to cast the object to another type. In class interfaces, the general form of a conversion operator is:

operator <type>() const; Conversion operators usually are member functions: they are automatically called when their objects are used as rvalues in expressions having a lvalue. Using a conversion operator a object may be interpreted as a rvalue, allowing us to perform the above assignment.

Conversion operators are somewhat dangerous. The conversion is automatically performed by the compiler and unless its use is perfectly transparent it may confuse those who read code in which conversion operators are used. E.g., novice C++ programmers are frequently confused by statements like `'.

As a rule of thumb: classes should define at most one conversion operator. Multiple conversion operators may be defined but frequently result in ambiguous code. E.g., if a class defines and then passing an object of this class to a function expecting a argument results in an ambiguity as an and a may both be used to initialize a .

In the current example, the class could define the following conversion operator for :

String::operator char const *() const { return d_string; } Notes:
  • Conversion operators do not define return types. The conversion operator returns a value of the type specified beyond the keyword.
  • In certain situations (e.g., when a argument is passed to a function specifying an ellipsis parameter) the compiler needs a hand to disambiguate our intentions. A solves the problem.
  • With template functions conversion operators may not work immediately as expected. For example, when defining a conversion operator then won't compile. The reason for this is explained in section 21.9, but a shortcut allowing the conversion operator to work is to define the following overloaded function: std::ostream &operator<<(std::ostream &out, std::string const &str) { return out.write(str.data(), str.length()); }
Conversion operators are also used when objects of classes defining conversion operators are inserted into streams. Realize that the right hand sides of insertion operators are function parameters that are initialized by the operator's right hand side arguments. The rules are simple:
  • If a class defining a conversion operator also defines an insertion operator accepting an object the insertion operator is used;
  • Otherwise, if the type returned by the conversion operator is insertable then the conversion operator is used;
  • Otherwise, a compilation error results. Note that this happens if the type returned by the conversion operator itself defines a conversion operator to a type that may be inserted into a stream.
In the following example an object of class is directly inserted; an object of the class uses the conversion operator; an object of the class cannot be inserted since it does not define an insertion operator and the type returned by its conversion operator cannot be inserted either (does define an , but the fact that a itself cannot be inserted causes the error): #include <iostream> #include <string> using namespace std; struct Insertable { operator int() const { cout << "op int()\n"; return 0; } }; ostream &operator<<(ostream &out, Insertable const &ins) { return out << "insertion operator"; } struct Convertor { operator Insertable() const { return Insertable(); } }; struct Text { operator int() const { return 1; } }; struct Error { operator Text() const { return Text{}; } }; int main() { Insertable insertable; cout << insertable << '\n'; Convertor convertor; cout << convertor << '\n'; Error error; cout << error << '\n'; }

Some final remarks regarding conversion operators:

  • A conversion operator should be a `natural extension' of the facilities of the object. For example, the stream classes define , allowing constructions like .
  • A conversion operator should return an rvalue. It should do so to enforce data-hiding and because it is the intended use of the conversion operator. Defining a conversion operator as an lvalue (e.g., defining an conversion operator) opens up a back door, and the operator can only be used as lvalue when explicitly called (as in: ). Don't use it.
  • Conversion operators should be defined as member functions as they don't modify their object's data members.
  • Conversion operators returning composed objects should return const references to these objects whenever possible to avoid calling the composed object's copy constructor.

11.4: The keyword `explicit'

Conversions are not only performed by conversion operators, but also by constructors accepting one argument (i.e., constructors having one or multiple parameters, specifying default argument values for all parameters or for all but the first parameter).

Assume a data base class is defined in which objects can be stored. It defines a pointer, and so it offers a copy constructor and an overloaded assignment operator.

In addition to the copy constructor offers a default constructor and several additional constructors:

  • : the initially contains a single object;
  • : the data about multiple persons are read from .
  • : the data of persons are read from , by default the standard input stream.

The above constructors all are perfectly reasonable. But they also allow the compiler to compile the following code without producing any warning at all:

DataBase db; DataBase db2; Person person; db2 = db; // 1 db2 = person; // 2 db2 = 10; // 3 db2 = cin; // 4 Statement 1 is perfectly reasonable: is used to redefine . Statement 2 might be understandable since we designed to contain objects. Nevertheless, we might question the logic that's used here as a is not some kind of . The logic becomes even more opaque when looking at statements 3 and 4. Statement 3 in effect waits for the data of 10 persons to appear at the standard input stream. Nothing like that is suggested by .

Implicit promotions are used with statements 2 through 4. Since constructors accepting, respectively a , an , and a and an have been defined for and since the assignment operator expects a right-hand side (rhs) argument the compiler first converts the rhs arguments to anonymous objects which are then assigned to .

It is good practice to prevent implicit promotions by using the modifier when declaring a constructor. Constructors using the modifier can only be used to construct objects explicitly. Statements 2-4 would not have compiled if the constructors expecting one argument would have been declared using . E.g.,

explicit DataBase(Person const &person); explicit DataBase(size_t count, std:istream &in);

Having declared all constructors accepting one argument as the above assignments would have required the explicit specification of the appropriate constructors, thus clarifying the programmer's intent:

DataBase db; DataBase db2; Person person; db2 = db; // 1 db2 = DataBase{ person }; // 2 db2 = DataBase{ 10 }; // 3 db2 = DataBase{ cin }; // 4 As a rule of thumb prefix one argument constructors with the keyword unless implicit promotions are perfectly natural ('s accepting constructor is a case in point).

11.4.1: Explicit conversion operators

In addition to explicit constructors, C++ supports explicit conversion operators.

For example, a class might define returning if an object of that class is in a usable state and if not. Since the type is an arithmetic type this could result in unexpected or unintended behavior. Consider:

void process(bool value); class StreamHandler { public: operator bool() const; // true: object is fit for use ... }; int fun(StreamHandler &sh) { int sx; if (sh) // intended use of operator bool() ... use sh as usual; also use `sx' process(sh); // typo: `sx' was intended } In this example unintentionally receives the value returned by using the implicit conversion from to .

When defining conversion operators implicit conversions like the one shown in the example are prevented. Such conversion operators can only be used in situations where the converted type is explicitly required (as in the condition clauses of or statements), or is explicitly requested using a . To declare an explicit bool conversion operator in class 's interface replace the above declaration by:

explicit operator bool() const;

Since the C++14 standard define an . As a consequence:

while (cin.get(ch)) // compiles OK ; bool fun1() { return cin; // 'bool = istream' won't compile as } // istream defines 'explicit operator bool' bool fun1() { return static_cast<bool>(cin); // compiles OK }

11.5: Overloading the increment and decrement operators

Overloading the increment operator () and decrement operator () introduces a small problem: there are two versions of each operator, as they may be used as postfix operator (e.g., ) or as prefix operator (e.g., ).

Used as postfix operator, the value's object is returned as an rvalue, temporary const object and the post-incremented variable itself disappears from view. Used as prefix operator, the variable is incremented, and its value is returned as lvalue and it may be altered again by modifying the prefix operator's return value. Whereas these characteristics are not required when the operator is overloaded, it is strongly advised to implement these characteristics in any overloaded increment or decrement operator.

Suppose we define a wrapper class around the value type. Such a class could offer the following (partially shown) interface:

class Unsigned { size_t d_value; public: Unsigned(); explicit Unsigned(size_t init); Unsigned &operator++(); } The class's last member declares the prefix overloaded increment operator. The returned lvalue is . The member is easily implemented: Unsigned &Unsigned::operator++() { ++d_value; return *this; }

To define the postfix operator, an overloaded version of the operator is defined, expecting a (dummy) argument. This might be considered a kludge, or an acceptable application of function overloading. Whatever your opinion in this matter, the following can be concluded:

  • Overloaded increment and decrement operators without parameters are prefix operators, and should return references to the current object.
  • Overloaded increment and decrement operators having an int parameter are postfix operators, and should return a value which is a copy of the object at the point where its postfix operator is used.
The postfix increment operator is declared as follows in the class 's interface: Unsigned operator++(int); It may be implemented as follows: Unsigned Unsigned::operator++(int) { Unsigned tmp{ *this }; ++d_value; return tmp; } Note that the operator's parameter is not used. It is only part of the implementation to disambiguate the prefix- and postfix operators in implementations and declarations.

In the above example the statement incrementing the current object offers the nothrow guarantee as it only involves an operation on a primitive type. If the initial copy construction throws then the original object is not modified, if the return statement throws the object has safely been modified. But incrementing an object could itself throw exceptions. How to implement the increment operators in that case? Once again, is our friend. Here are the pre- and postfix operators offering the strong guarantee when the member performing the increment operation may throw:

Unsigned &Unsigned::operator++() { Unsigned tmp{ *this }; tmp.increment(); swap(tmp); return *this; } Unsigned Unsigned::operator++(int) { Unsigned tmp{ *this }; tmp.increment(); swap(tmp); return tmp; } Both operators first create copies of the current objects. These copies are incremented and then swapped with the current objects. If throws the current objects remain unaltered; the swap operations ensure that the correct objects are returned (the incremented object for the prefix operator, the original object for the postfix operator) and that the current objects become the incremented objects.

When calling the increment or decrement operator using its full member function name then any argument passed to the function results in calling the postfix operator. Omitting the argument results in calling the prefix operator. Example:

Unsigned uns{ 13 }; uns.operator++(); // prefix-incrementing uns uns.operator++(0); // postfix-incrementing uns

Both the prefix and postfix increment and decrement operators are deprecated when applied to type of variables. In situations where a postfix increment operator could be useful the (cf. section 19.1.11) should be used.

11.6: Overloading binary operators

In various classes overloading binary operators (like ) can be a very natural extension of the class's functionality. For example, the class has various overloaded members.

Most binary operators come in two flavors: the plain binary operator (like the operator) and the compound binary assignment operator (like ). Whereas the plain binary operators return values, the compound binary assignment operators usually return references to the objects for which the operators were called. For example, with objects the following code (annotations below the example) may be used:

std::string s1; std::string s2; std::string s3; s1 = s2 += s3; // 1 (s2 += s3) + " postfix"; // 2 s1 = "prefix " + s3; // 3 "prefix " + s3 + "postfix"; // 4
  • at the contents of is added to . Next, is returned, and its new contents are assigned to . Note that returns .
  • at the contents of is also added to , but as returns itself, it's possible to add some more to
  • at the operator returns a containing the concatenation of the text and the contents of . This string returned by the operator is thereupon assigned to .
  • at the operator is applied twice. The effect is:
    1. The first returns a containing the concatenation of the text and the contents of .
    2. The second operator takes this returned string as its left hand value, and returns a string containing the concatenated text of its left and right hand operands.
    3. The string returned by the second operator represents the value of the expression.

Now consider the following code, in which a class supports an overloaded :

class Binary { public: Binary(); Binary(int value); Binary operator+(Binary const &rhs); }; int main() { Binary b1; Binary b2{ 5 }; b1 = b2 + 3; // 1 b1 = 3 + b2; // 2 } Compilation of this little program fails for statement , with the compiler reporting an error like: error: no match for 'operator+' in '3 + b2' Why is statement compiled correctly whereas statement won't compile?

In order to understand this remember promotions. As we have seen in section 11.4, constructors expecting single arguments may implicitly be activated when an argument of an appropriate type is provided. We've already encountered this with objects, where NTBSs may be used to initialize objects.

Analogously, in statement , is called, using as its left-hand side operand. This operator expects another object as its right-hand side operand. However, an is provided. But as a constructor exists, the value can be promoted to a object. Next, this object is passed as argument to the member.

Unfortunately, in statement promotions are not available: here the operator is applied to an -type lvalue. An is a primitive type and primitive types have no knowledge of `constructors', `member functions' or `promotions'.

How, then, are promotions of left-hand operands implemented in statements like ? Since promotions can be applied to function arguments, we must make sure that both operands of binary operators are arguments. This implies that plain binary operators supporting promotions for either their left-hand side operand or right-hand side operand should be declared as free operators, also called free functions.

Functions like the plain binary operators conceptually belong to the class for which they implement these operators. Consequently they should be declared in the class's header file. We cover their implementations shortly, but here is our first revision of the declaration of the class , declaring an overloaded operator as a free function:

class Binary { public: Binary(); Binary(int value); }; Binary operator+(Binary const &lhs, Binary const &rhs);

After defining binary operators as free functions, several promotions are available:

  • If the left-hand operand is of the intended class type, the right hand argument is promoted whenever possible;
  • If the right-hand operand is of the intended class type, the left hand argument is promoted whenever possible;
  • No promotions occur when neither operand is of the intended class type;
  • An ambiguity occurs when promotions to different classes are possible for the two operands. For example: class A; class B { public: B(A const &a); }; class A { public: A(); A(B const &b); }; A operator+(A const &a, B const &b); B operator+(B const &b, A const &a); int main() { A a; a + a; }; Here, both overloaded operators are possible candidates when compiling the statement . The ambiguity must be solved by explicitly promoting one of the arguments, e.g., , which enables the compiler to resolve the ambiguity to the first overloaded operator.

The next step consists of implementing the required overloaded binary compound assignment operators, having the form , where represents a binary operator. As these operators always have left-hand side operands which are object of their own classes, they are implemented as genuine member functions. Compound assignment operators usually return references to the objects for which the binary compound assignment operators were requested, as these objects might be modified in the same statement. E.g., .

Here is our second revision of the class , showing the declaration of the plain binary operator as well as the corresponding compound assignment operator:

class Binary { public: Binary(); Binary(int value); Binary &operator+=(Binary const &rhs); }; Binary operator+(Binary const &lhs, Binary const &rhs);

How should the compound addition assignment operator be implemented? When implementing compound binary assignment operators the strong guarantee should always be kept in mind: if the operation might throw use a temporary object and swap. Here is our implementation of the compound assignment operator:

Binary &Binary::operator+=(Binary const &rhs) { Binary tmp{ *this }; tmp.add(rhs); // this might throw swap(tmp); return *this; }

It's easy to implement the free binary operator: the argument is copied into a to which the operand is added. Then is returned, using copy elision. The class declares the free binary operator as a friend (cf. chapter 15), so it can call member:

class Binary { friend Binary operator+(Binary const &lhs, Binary const &rhs); public: Binary(); Binary(int value); Binary &operator+=(Binary const &other); private: void add(Binary const &other); };

The binary operator's implementation becomes:

Binary operator+(Binary const &lhs, Binary const &rhs) { Binary tmp{ lhs }; tmp.add(rhs); return tmp; }

If the class is move-aware then it's attractive to add move-aware binary operators. In this case we also need operators whose left-hand side operands are rvalue references. When a class is move aware various interesting implementations are suddenly possible, which we encounter below, and in the next (sub)section. First have a look at the signature of such a binary operator (which should also be declared as a friend in the class interface):

Binary operator+(Binary &&lhs, Binary const &rhs);

Since the lhs operand is an rvalue reference, we can modify it ad lib. Binary operators are commonly designed as factory functions, returning objects created by those operators. However, the (modified) object referred to by should itself not be returned. As stated in the C++ standard,

A temporary object bound to a reference parameter in a function call persists until the completion of the full-expression containing the call.
and furthermore:
The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
In other words, a temporary object cannot itself be returned as the function's return value: a return type should therefore not be used. Therefore functions implementing binary operators are factory functions (note, however, that the returned object may be constructed using the class's move constructor whenever a temporary object has to be returned).

Alternatively, the binary operator can first create an object by move constructing it from the operator's lhs operand, performing the binary operation on that object and the operator's rhs operand, and then return the modified object (allowing the compiler to apply copy elision). It's a matter of taste which one is preferred.

Here are the two implementations. Because of copy elision the explicitly defined object is created in the location of the return value. Both implementations, although they appear to be different, show identical run-time behavior:

// first implementation: modify lhs Binary operator+(Binary &&lhs, Binary const &rhs) { lhs.add(rhs); return std::move(lhs); } // second implementation: move construct ret from lhs Binary operator+(Binary &&lhs, Binary const &rhs) { Binary ret{ std::move(lhs) }; ret.add(rhs); return ret; } Now, when executing expressions like (all objects) the following functions are called: copy operator+ = b1 + b2 Copy constructor = tmp(b1) adding = tmp.add(b2) copy elision : tmp is returned from b1 + b2 move operator+ = tmp + b3 adding = tmp.add(b3) Move construction = tmp2(move(tmp)) is returned

But we're not there yet: in the next section we encounter possibilities for several more interesting implementations, in the context of compound assignment operators.

11.6.1: Member function reference bindings (& and &&)

We've seen that binary operators (like ) can be implemented very efficiently, but require at least move constructors.

An expression like

Binary{} + varB + varC + varD therefore returns a move constructed object representing , then another move constructed object receiving the first return value and , and finally yet another move constructed object receiving the second returned object and as its arguments.

Now consider the situation where we have a function defining a parameter, and a second parameter. Inside that function these values need to be added, and their sum is then passed as argument to two other functions. We could do this:

void fun1(Binary &&lhs, Binary const &rhs) { lhs += rhs; fun2(lhs); fun3(lhs); } But realize that when using we first construct a copy of the current object, so a temporary object is available to perform the addition on, and then swap the temporary object with the current object to commit the results. But wait! Our lhs operand already is a temporary object. So why create another?

In this example another temporary object is indeed not required: remains in existence until ends. But different from the binary operators the binary compound assignment operators don't have explicitly defined left-hand side operands. But we still can inform the compiler that a particular member (so, not merely compound assignment operators) should only be used when the objects calling those members is an anonymous temporary object, or a non-anonymous (modifiable or non-modifiable) object. For this we use reference bindings a.k.a. reference qualifiers.

Reference bindings consist of a reference token (), optionally preceded by , or an rvalue reference token (). Such reference qualifiers are immediately affixed to the function's head (this applies to the declaration and the implementation alike). Functions provided with rvalue reference bindings are selected by the compiler when used by anonymous temporary objects, whereas functions provided with lvalue reference bindings are selected by the compiler when used by other types of objects.

Reference qualifiers allow us to fine-tune our implementations of compund assignment operators like . If we know that the object calling the compound assignment operator is itself a temporary, then there's no need for a separate temporary object. The operator may directly perform its operation and could then return itself as an rvalue reference. Here is the implementation of tailored to being used by temporary objects:

Binary &&Binary::operator+=(Binary const &rhs) && { add(rhs); // directly add rhs to *this, return std::move(*this); // return the temporary object itself } This implementation is about as fast as it gets. But be careful: in the previous section we learned that a temporary is destroyed at the end of the full expression of a return stattement. In this case, however, the temporary already exists, and so (also see the previous section) it should persist until the expression containing the () function call is completed. As a consequence, cout << (Binary{} += existingBinary) << '\n'; is OK, but Binary &&rref = (Binary{} += existingBinary); cout << rref << '\n'; is not, since becomes a dangling reference immediately after its initialization.

A full-proof alternative implementation of the rvalue-reference bound returns a move-constructed copy:

Binary Binary::operator+=(Binary const &rhs) && { add(rhs); // directly add rhs to *this, return std::move(*this); // return a move constructed copy } The price to pay for this full-proof implementation is an extra move construction. Now, using the previous example (using ), returns a copy of the temporary, which is still a temporary object which can safely be referred to by .

Which implementation to use may be a matter of choice: if users of know what they're doing then the former implementation can be used, since these users will never use the above initialization. If you're not so sure about your users, use the latter implementation: formally your users will do something they shouldn't do, but there's no penalty for that.

For the compound assignment operator called by an lvalue reference (i.e., a named object) we use the implementation for from the previous section (note the reference qualifier):

Binary &Binary::operator+=(Binary const &other) & { Binary tmp(*this); tmp.add(other); // this might throw swap(tmp); return *this; } With this implementation adding objects to each other (e.g., ) boils down to operator+= (&) = b2 += b3 Copy constructor = tmp(b2) adding = tmp.add(b3) swap = b2 <-> tmp return = b2 operator+= (&) = b1 += b2 Copy constructor = tmp(b1) adding = tmp.add(b2) swap = b1 <-> tmp return = b1

When the leftmost object is a temporary then a copy construction and swap call are replaced by the construction of an anonymous object. E.g., with we observe:

operator+= (&) = b2 += b3 Copy constructor = tmp(b2) adding = tmp.add(b3) swap = b2 <-> tmp Anonymous object = Binary{} operator+= (&&) = Binary{} += b2 adding = add(b2) return = move(Binary{})

For an alternative implementation exists, using one single return statement, but in fact requiring two extra function calls. It's a matter of taste whether you prefer less code to write or executing fewer function calls:

Binary &Binary::operator+=(Binary const &other) & { return *this = Binary{*this} += rhs; } Notice that the implementations of and are independent of the actual definition of the class . Adding standard binary operators to a class (i.e., operators operating on arguments of their own class types) can therefore easily be realized.

11.7: Overloading `operator new(size_t)'

When is overloaded, it must define a return type, and its first parameter must be of type . The default defines only one parameter, but overloaded versions may define multiple parameters. The first one is not explicitly specified but is deduced from the size of objects of the class for which is overloaded. In this section overloading is discussed. Overloading is discussed in section 11.9.

It is possible to define multiple versions of the , as long as each version defines its own unique set of arguments. When overloaded members must dynamically allocate memory they can do so using the global , applying the scope resolution operator . In the next example the overloaded of the class initializes the substrate of dynamically allocated objects to 0-bytes:

#include <cstring> #include <iosfwd> class String { std::string *d_data; public: void *operator new(size_t size) { return memset(::operator new(size), 0, size); } bool empty() const { return d_data == 0; } }; The above is used in the following program, illustrating that even though 's default constructor does nothing the object's data member is initialized to zero: #include "string.h" #include <iostream> using namespace std; int main() { String *sp = new String; cout << boolalpha << sp->empty() << '\n'; // shows: true } At the following took place:
  • First, was called, allocating and initializing a block of memory, the size of a object.
  • Next, a pointer to this block of memory was passed to the (default) constructor. Since no constructor was defined, the constructor itself didn't do anything at all.
As initialized the allocated memory to zero bytes the allocated object's member had already been initialized to a 0-pointer by the time it started to exist.

All member functions (including constructors and destructors) we've encountered so far define a (hidden) pointer to the object on which they should operate. This hidden pointer becomes the function's pointer.

In the next example of pseudoC++code, the pointer is explicitly shown to illustrate what's happening when is used. In the first part a object is directly defined, in the second part of the example the (overloaded) is used:

String::String(String *const this); // real prototype of the default // constructor String *sp = new String; // This statement is implemented // as follows: String *sp = static_cast<String *>( // allocation String::operator new(sizeof(String)) ); String::String(sp); // initialization In the above fragment the member functions were treated as object-less member functions of the class . Such members are called static member functions (cf. chapter 8). Actually, is such a static member function. Since it has no pointer it cannot reach data members of the object for which it is expected to make memory available. It can only allocate and initialize the allocated memory, but cannot reach the object's data members by name as there is as yet no data object layout defined.

Following the allocation, the memory is passed (as the pointer) to the constructor for further processing.

can have multiple parameters. The first parameter is initialized as an implicit argument and is always a parameter. Additional overloaded operators may define additional parameters. An interesting additional is the placement new operator. With the placement new operator a block of memory has already been set aside and one of the class's constructors is used to initialize that memory. Overloading placement new requires an having two parameters: and , pointing to the memory that was already available. The parameter is implicitly initialized, but the remaining parameters must explicitly be initialized using arguments to . Hence we reach the familiar syntactical form of the placement new operator in use:

char buffer[sizeof(String)]; // predefined memory String *sp = new(buffer) String; // placement new call The declaration of the placement new operator in our class looks like this: void *operator new(size_t size, char *memory); It could be implemented like this (also initializing the 's memory to 0-bytes): void *String::operator new(size_t size, char *memory) { return memset(memory, 0, size); } Any other overloaded version of could also be defined. Here is an example showing the use and definition of an overloaded storing the object's address immediately in an existing array of pointers to objects (assuming the array is large enough): // use: String *next(String **pointers, size_t *idx) { return new(pointers, (*idx)++) String; } // implementation: void *String::operator new(size_t size, String **pointers, size_t idx) { return pointers[idx] = ::operator new(size); }

11.8: Overloading `operator delete(void *)'

The operator may also be overloaded. In fact it's good practice to overload whenever is also overloaded.

must define a parameter. A second overloaded version defining a second parameter of type is related to overloading and is discussed in section 11.9.

Overloaded members return .

The `home-made' is called when deleting a dynamically allocated object after executing the destructor of the associated class. So, the statement

delete ptr; with being a pointer to an object of the class for which the operator was overloaded, is a shorthand for the following statements: ptr->~String(); // call the class's destructor // and do things with the memory pointed to by ptr String::operator delete(ptr); The overloaded may do whatever it wants to do with the memory pointed to by . It could, e.g., simply delete it. If that would be the preferred thing to do, then the default operator can be called using the scope resolution operator. For example: void String::operator delete(void *ptr) { // any operation considered necessary, then, maybe: ::delete ptr; } To declare the above overloaded simply add the following line to the class's interface: void operator delete(void *ptr); Like is a static member function (see also chapter 8).

11.9: Operators `new[]' and `delete[]'

In sections 9.1.1, 9.1.2 and 9.2.1 and were introduced. Like and the operators and may be overloaded.

As it is possible to overload and as well as and , one should be careful in selecting the appropriate set of operators. The following rule of thumb should always be applied:

If is used to allocate memory, should be used to deallocate memory. If is used to allocate memory, should be used to deallocate memory.

By default these operators act as follows:

  • is used to allocate a single object or primitive value. With an object, the object's constructor is called.
  • is used to return the memory allocated by . Again, with class-type objects, the class's destructor is called.
  • is used to allocate a series of primitive values or objects. If a series of objects is allocated, the class's default constructor is called to initialize each object individually.
  • is used to delete the memory previously allocated by . If objects were previously allocated, then the destructor is called for each individual object. Be careful, though, when pointers to objects were allocated. If pointers to objects were allocated the destructors of the objects to which the allocated pointers point won't automatically be called. A pointer is a primitive type and so no further action is taken when it is returned to the common pool.

11.9.1: Overloading `new[]'

To overload in a class (e.g., in the class ) add the following line to the class's interface: void *operator new[](size_t size); The member's parameter is implicitly provided and is initialized by C++'s run-time system to the amount of memory that must be allocated. Like the simple one-object it should return a . The number of objects that must be initialized can easily be computed from (and of course replacing by the appropriate class name when overloading for another class). The overloaded member may allocate raw memory using e.g., the default or the default : void *operator new[](size_t size) { return ::operator new[](size); // alternatively: // return ::operator new(size); } Before returning the allocated memory the overloaded has a chance to do something special. It could, e.g., initialize the memory to zero-bytes.

Once the overloaded has been defined, it is automatically used in statements like:

String *op = new String[12]; Like additional overloads of may be defined. One opportunity for an overload is overloading placement new specifically for arrays of objects. This operator is available by default but becomes unavailable once at least one overloaded is defined. Implementing placement is not difficult. Here is an example, initializing the available memory to 0-bytes before returning: void *String::operator new[](size_t size, char *memory) { return memset(memory, 0, size); } To use this overloaded operator, the second parameter must again be provided, as in: char buffer[12 * sizeof(String)]; String *sp = new(buffer) String[12];

11.9.2: Overloading `delete[]'

To overload in a class add the following line to the class's interface: void operator delete[](void *memory); Its parameter is initialized to the address of a block of memory previously allocated by .

There are some subtleties to be aware of when implementing . Although the addresses returned by and point to the allocated object(s), there is an additional value available immediately before the address returned by and . This value is part of the allocated block and contains the actual size of the block. This of course does not hold true for the placement operator.

When a class defines a destructor the value preceding the address returned by does not contain the size of the allocated block, but the number of objects specified when calling . Normally that is of no interest, but when overloading it might become a useful piece of information. In those cases does not receive the address returned by but rather the address of the initial value. Whether this is at all useful is not clear. By the time 's code is executed all objects have already been destroyed, so is only to determine how many objects were destroyed but the objects themselves cannot be used anymore.

Here is an example showing this behavior of for a minimal class:

struct Demo { size_t idx; Demo() { cout << "default cons\n"; } ~Demo() { cout << "destructor\n"; } void *operator new[](size_t size) { return ::operator new(size); } void operator delete[](void *vp) { cout << "delete[] for: " << vp << '\n'; ::operator delete[](vp); } }; int main() { Demo *xp; cout << ((int *)(xp = new Demo[3]))[-1] << '\n'; cout << xp << '\n'; cout << "==================\n"; delete[] xp; } // This program displays (your 0x?????? addresses might differ, but // the difference between the two should be sizeof(size_t)): // default cons // default cons // default cons // 3 // 0x8bdd00c // ================== // destructor // destructor // destructor // delete[] for: 0x8bdd008 Having overloaded for a class , it will be used automatically in statements like: delete[] new String[5];

Operator may also be overloaded using an additional parameter:

void operator delete[](void *p, size_t size); Here is automatically initialized to the size (in bytes) of the block of memory to which points. If this form is defined, then should not be defined, to avoid ambiguities. An example of this latter form of is: void String::operator delete[](void *p, size_t size) { cout << "deleting " << size << " bytes\n"; ::operator delete[](ptr); }

Additional overloads of may be defined, but to use them they must explicitly be called as static member functions (cf. chapter 8). Example:

// declaration: void String::operator delete[](void *p, ostream &out); // usage: String *xp = new String[3]; String::operator delete[](xp, cout);

11.9.3: The `operator delete(void *, size_t)' family

As we've seen classes may overload their and members.

Since the C++14 standard the global and functions can also be overloaded.

When a global sized deallocation function is defined, it is automatically used instead of the default, non-sized deallocation function. The performance of programs may improve if a sized deallocation function is available (cf. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3663.html).

11.9.4: `new[]', `delete[]' and exceptions

When an exception is thrown while executing a expression, what will happen? In this section we'll show that is exception safe even when only some of the objects were properly constructed.

To begin, might throw while trying to allocate the required memory. In this case a is thrown and we don't leak as nothing was allocated.

Having allocated the required memory the class's default constructor is going to be used for each of the objects in turn. At some point a constructor might throw. What happens next is defined by the C++ standard: the destructors of the already constructed objects are called and the memory allocated for the objects themselves is returned to the common pool. Assuming that the failing constructor offers the basic guarantee is therefore exception safe even if a constructor may throw.

The following example illustrates this behavior. A request to allocate and initialize five objects is made, but after constructing two objects construction fails by throwing an exception. The output shows that the destructors of properly constructed objects are called and that the allocated substrate memory is properly returned:

#include <iostream> using namespace std; static size_t count = 0; class X { int x; public: X() { if (count == 2) throw 1; cout << "Object " << ++count << '\n'; } ~X() { cout << "Destroyed " << this << "\n"; } void *operator new[](size_t size) { cout << "Allocating objects: " << size << " bytes\n"; return ::operator new(size); } void operator delete[](void *mem) { cout << "Deleting memory at " << mem << ", containing: " << *static_cast<int *>(mem) << "\n"; ::operator delete(mem); } }; int main() try { X *xp = new X[5]; cout << "Memory at " << xp << '\n'; delete[] xp; } catch (...) { cout << "Caught exception.\n"; } // Output from this program (your 0x??? addresses might differ) // Allocating objects: 24 bytes // Object 1 // Object 2 // Destroyed 0x8428010 // Destroyed 0x842800c // Deleting memory at 0x8428008, containing: 5 // Caught exception.

11.10: Function Objects

Function Objects are created by overloading the function call operator. By defining the function call operator an object masquerades as a function, hence the term function objects. Function objects are also known as functors.

Function objects are important when using generic algorithms. The use of function objects is preferred over alternatives like pointers to functions. The fact that they are important in the context of generic algorithms leaves us with a didactic dilemma. At this point in the C++ Annotations it would have been nice if generic algorithms would already have been covered, but for the discussion of the generic algorithms knowledge of function objects is required. This bootstrapping problem is solved in a well known way: by ignoring the dependency for the time being, for now concentrating on the function object concept.

Function objects are objects for which has been defined. Function objects are not just used in combination with generic algorithms, but also as a (preferred) alternative to pointers to functions.

Function objects are frequently used to implement predicate functions. Predicate functions return boolean values. Predicate functions and predicate function objects are commonly referred to as `predicates'. Predicates are frequently used by generic algorithms such as the count_if generic algorithm, covered in chapter 19, returning the number of times its function object has returned . In the standard template library two kinds of predicates are used: unary predicates receive one argument, binary predicates receive two arguments.

Assume we have a class and an array of objects. Further assume that the array is not sorted. A well known procedure for finding a particular object in the array is to use the function , which performs a lineair search in an array. Example:

Person &target = targetPerson(); // determine the person to find Person *pArray; size_t n = fillPerson(&pArray); cout << "The target person is"; if (!lsearch(&target, pArray, &n, sizeof(Person), compareFunction)) cout << " not"; cout << "found\n"; The function determines the person we're looking for, and is called to fill the array. Then is used to locate the target person.

The comparison function must be available, as its address is one of the arguments of . It must be a real function having an address. If it is defined inline then the compiler has no choice but to ignore that request as inline functions don't have addresses. could be implemented like this:

int compareFunction(void const *p1, void const *p2) { return *static_cast<Person const *>(p1) // lsearch wants 0 != // for equal objects *static_cast<Person const *>(p2); } This, of course, assumes that the has been overloaded in the class . But overloading is no big deal, so let's assume that that operator is actually available.

On average times at least the following actions take place:

  1. The two arguments of the compare function are pushed on the stack;
  2. The value of the final parameter of is determined, producing 's address;
  3. The compare function is called;
  4. Then, inside the compare function the address of the right-hand argument of the argument is pushed on the stack;
  5. is evaluated;
  6. The argument of the function is popped off the stack;
  7. The two arguments of the compare function are popped off the stack.
Using function objects results in a different picture. Assume we have constructed a function , having the following prototype (this, however, is not the preferred approach. Normally a generic algorithm is preferred over a home-made function. But for now we focus on to illustrate the use and implementation of a function object): Person const *PersonSearch(Person *base, size_t nmemb, Person const &target); This function can be used as follows: Person &target = targetPerson(); Person *pArray; size_t n = fillPerson(&pArray); cout << "The target person is"; if (!PersonSearch(pArray, n, target)) cout << " not"; cout << "found\n"; So far, not much has been changed. We've replaced the call to with a call to another function: . Now look at itself: Person const *PersonSearch(Person *base, size_t nmemb, Person const &target) { for (int idx = 0; idx < nmemb; ++idx) if (target(base[idx])) return base + idx; return 0; } implements a plain linear search. However, in the for-loop we see . Here is used as a function object. Its implementation is simple: bool Person::operator()(Person const &other) const { return *this == other; } Note the somewhat peculiar syntax: . The first set of parentheses define the operator that is overloaded: the function call operator. The second set of parentheses define the parameters that are required for this overloaded operator. In the class header file this overloaded operator is declared as: bool operator()(Person const &other) const; Clearly is a simple function. It contains but one statement, and we could consider defining it inline. Assuming we do, then this is what happens when is called:
  1. The address of the right-hand argument of the argument is pushed on the stack;
  2. The function is evaluated (which probably also is a semantic improvement over calling when looking for an object equal to a specified target object);
  3. The argument of argument is popped off the stack.
Due to the fact that is an inline function, it is not actually called. Instead is called immediately. Moreover, the required stack operations are fairly modest.

Function objects may truly be defined inline. Functions that are called indirectly (i.e., using pointers to functions) can never be defined inline as their addresses must be known. Therefore, even if the function object needs to do very little work it is defined as an ordinary function if it is going to be called through pointers. The overhead of performing the indirect call may annihilate the advantage of the flexibility of calling functions indirectly. In these cases using inline function objects can result in an increase of a program's efficiency.

An added benefit of function objects is that they may access the private data of their objects. In a search algorithm where a compare function is used (as with ) the target and array elements are passed to the compare function using pointers, involving extra stack handling. Using function objects, the target person doesn't vary within a single search task. Therefore, the target person could be passed to the function object's class constructor. This is in fact what happens in the expression receiving as its only argument the subsequent elements of the array to search.

11.10.1: Constructing manipulators

In chapter 6 we saw constructions like << << << to display the value 13 in hexadecimal format. One may wonder by what magic the manipulator accomplishes this. In this section the construction of manipulators like is covered.

Actually the construction of a manipulator is rather simple. To start, a definition of the manipulator is needed. Let's assume we want to create a manipulator which sets the field width of the next field to be written by the object to 10. This manipulator is constructed as a function. The function needs to know about the object in which the width must be set. By providing the function with an parameter, it obtains this knowledge. Now that the function knows about the object we're referring to, it can set the width in that object.

Next, it must be possible to use the manipulator in an insertion sequence. This implies that the return value of the manipulator must be a reference to an object also.

From the above considerations we're now able to construct our function:

#include <ostream> #include <iomanip> std::ostream &w10(std::ostream &str) { return str << std::setw(10); } The function can of course be used in a `stand alone' mode, but it can also be used as a manipulator. E.g., #include <iostream> #include <iomanip> using namespace std; extern ostream &w10(ostream &str); int main() { w10(cout) << 3 << " ships sailed to America\n"; cout << "And " << w10 << 3 << " more ships sailed too.\n"; } The function can be used as a manipulator because the has an overloaded << accepting a pointer to a function expecting an and returning an . Its definition is: ostream& operator<<(ostream &(*func)(ostream &str)) { return (*func)(*this); } In addition to the above overloaded << another one is defined ios_base &operator<<(ios_base &(*func)(ios_base &base)) { (*func)(*this); return *this; } This latter function is used when inserting, e.g., or .

The above procedure does not work for manipulators requiring arguments. It is of course possible to overload << to accept an reference and the address of a function expecting an and, e.g., an , but while the address of such a function may be specified with the <<-operator, the arguments itself cannot be specified. So, one wonders how the following construction has been implemented:

cout << setprecision(3) In this case the manipulator is defined as a macro. Macro's, however, are the realm of the preprocessor, and may easily suffer from unwelcome side-effects. In C++ programs they should be avoided whenever possible. The following section introduces a way to implement manipulators requiring arguments without resorting to macros, but using anonymous objects.

11.10.1.1: Manipulators requiring arguments

Manipulators taking arguments are implemented as macros: they are handled by the preprocessor, and are not available beyond the preprocessing stage.

//The problem appears to be that you can't call a function in an //insertion sequence: when using multiple << operators in one statement //the compiler calls the functions, saves their return values, and then //uses their return values in the insertion sequence. That invalidates the //ordering of the arguments passed to your <<-operators. // //So, one might consider constructing another overloaded << accepting //the address of a function receiving not just the reference, but a //series of other arguments as well. But this creates the problem that it isn't //clear how the function should receive its arguments: you can't just call it //since that takes us back to the above-mentioned problem. Merely passing its //address is fine, but then no arguments can be passed to the function.

Manipulators, maybe requiring arguments, can also be defined without using macros. One solution, suitable for modifying globally available objects (like , or ) is based on using anonymous objects:

  • First, a class is defined, e.g. , whose constructor expects the arguments configuring the required manipulation. In our example representing, respectively, a field width and an alignment type.
  • The class also supports an overloaded insertion (or extraction) operator. E.g., ostream &operator<<(ostream &ostr, Align const &align)
  • Next, the (anonymous) object is inserted into a stream. The insertion operator passes the stream to , allowing that member to configure (and return) the provided stream.
Here is an example of a little program using such a home-made manipulator expecting multiple arguments: #include <iostream> #include <iomanip> class Align { unsigned d_width; std::ios::fmtflags d_alignment; public: Align(unsigned width, std::ios::fmtflags alignment); std::ostream &operator()(std::ostream &ostr) const; }; Align::Align(unsigned width, std::ios::fmtflags alignment) : d_width(width), d_alignment(alignment) {} std::ostream &Align::operator()(std::ostream &ostr) const { ostr.setf(d_alignment, std::ios::adjustfield); return ostr << std::setw(d_width); } std::ostream &operator<<(std::ostream &ostr, Align &&align) { return align(ostr); } using namespace std; int main() { cout << "`" << Align{ 5, ios::left } << "hi" << "'" << "`" << Align{ 10, ios::right } << "there" << "'\n"; } /* Generated output: `hi '` there' */

When (local) objects must be manipulated, then the class that must provide manipulators may define function call operators receiving the required arguments. E.g., consider a class that should allow its users to specify the value and line separators when inserting the matrix into an .

Two data members (e.g., and ) are defined (and initialized to acceptable values). The insertion function inserts between values, and at the end of inserted rows. The member simply assigns values to the corresponding data members.

Given an object , then at this point can be called. The function call operator should probably not insert the matrix, as the responsibility of manipulators is to manipulate, not to insert. So, to insert a matrix a statement like

cout << matrix(" ", "\n") << matrix << '\n'; should probably be used. The manipulator (i.e., function call operator) assigns the proper values to and , which are then used during the actual insertion.

The return value of the function call operator remains to be specified. The return value should be insertable, but in fact should not insert anything at all. An empty NTBS could be returned, but that's a bit kludge-like. Instead the address of a manipulator function, not performing any action, can be returned. Here's the implementation of such an empty manipulator:

// static (alternatively a free function could be used) std::ostream &Matrix::nop(std::ostream &out) { return out; } Thus, the implementation of the manipulator becomes: std::ostream &( *Matrix::operator()(char const *valueSep, char const *lineSep) ) (std::ostream &) { d_valueSep = valueSep; d_lineSep = lineSep; return nop; }

11.11: Lambda expressions

C++ supports lambda expressions. As we'll see in chapter 19generic algorithms often accept arguments that can either be function objects or plain functions. Examples are the (cf. section 19.1.58) and (cf. section 19.1.17) generic algorithms. As a rule of thumb: when a called function must remember its state a function object is appropriate, otherwise a plain function can be used.

Frequently the function or function object is not readily available, and it must be defined in or near the location where it is used. This is commonly realized by defining a class or function in the anonymous namespace (say: class or function A), passing an A to the code needing A. If that code is itself a member function of the class B, then A's implementation might benefit from having access to the members of class B.

This scheme usually results in a significant amount of code (defining the class), or it results in complex code (to make available software elements that aren't automatically accessible to A's code). It may also result in code that is irrelevant at the current level of specification. Nested classes don't solve these problems either. Moreover, nested classes can't be used in templates.

lambda expressions solve these problems. A lambda expression defines an anonymous function object which may immediately be passed to functions expecting function object arguments, as explained in the next few sections.

According to the C++ standard, lambda expressions provide a concise way to create simple function objects. The emphasis here is on simple: a lambda expression's size should be comparable to the size of inline-functions: just one or maybe two statements. If you need more code, then encapsulate that code in a separate function which is then called from inside the lambda expression's compound statement, or consider designing a separate function object.

11.11.1: Lambda expressions: syntax

A lambda expression defines an anonymous function object, also called a closure object. When a lambda expression is evaluated it results in a temporary object (the closure object). The type of a closure object is called its closure type.

Lambda expressions are used inside blocks, classes or namespaces (i.e., pretty much anywhere you like). Their implied closure type is defined in the smallest block, class or namespace scope containing the lambda expression. The closure object's visibility starts at its point of definition and ends where its closure type ends.

The closure type defines a () public inline function call operator. Here is an example of a lambda expression:

[] // the `lambda-introducer' (int x, int y) // the `lambda-declarator' { // a normal compound-statement return x * y; } The function call operator of the closure object created by this lambda expression expects two arguments and returns their product. It is an inline member of the closure type. To drop the attribute, the lambda expression should specify , as follows: [](int x, int y) mutable ... The lambda-declarator may be omitted, if no parameters are defined, but when specifying (or , see below) the lambda-declarator must at least start with an empty set of parentheses. The parameters in a lambda declarator cannot be given default arguments.

Declarator specifiers can be , , or both. A lambda-expression is itself a , which may be compile-time evaluated if its arguments qualify as const-expressions. Moreover, if a lambda-expression is defined inside a function then the lambda-expression itself must qualify as a , and explicitly specifying the declarator specifier is not required. The following function definitions, therefore, are identical:

int constexpr change10(int n) { return [n] { return n > 10 ? n - 10 : n + 10; }(); } int constexpr change10(int n) { return [n] () constexpr { return n > 10 ? n - 10 : n + 10; }(); }

A closure object as defined by the previous lambda expression could be used e.g., in combination with the (cf. section 19.1.1) generic algorithm to compute the product of a series of values stored in a vector:

cout << accumulate(vi.begin(), vi.end(), 1, [](int x, int y) { return x * y; }); The above lambda function uses the implicit return type . An implicit return type can be used in these cases:
  • the lambda expression does not contain a statement (i.e., a void lambda expression);
  • the lambda expression contains a single statement; or
  • the lambda expression contains multiple statements returning values of identical types (e.g., all values).

If there are multiple statements returning values of different types then the lambda expression's return type must specified be explicitly using a late-specified return type, (cf. section 3.3.6):

[](int x, int y) -> int { return y < 0 ? x / static_cast<double>(y) : z + x; }

Variables that are visible at the location of a lambda expression can be accessed by the lambda expression. How these variables are accessed depends on the contents of the lambda-introducer (the area between the square brackets, called the lambda-capture). The lambda-capture allows passing a local context to lambda expressions.

This chapter explains the features, technical details and syntaxes of the C++ programming language. I assume that you could write some simple programs. Otherwise, read "Introduction To C++ Programming for Novices and First-time Programmers".

To be a proficient programmer, you need to master two things: (1) the syntax of the programming language, and (2) the core libraries (i.e., ) associated with the language.

Introduction to C++

C++ Standards

C++ is standardized as ISO/IEC 14882. Currently, there are two versions:

  1. C++98 (ISO/IEC 14882:1998): 1st standard version of C++.
  2. C++03 (ISO/IEC 14882:2003): minor "bug-fix" to C++98 with no change to the language. Commonly refer to as C++98/C++03 or First C++ standard.
  3. C++11 (ISO/IEC 14882:2011): 2nd standard version of C++. Formerly called C++0x, as it was expected to finalize in 200x. It adds some new features to the language; more significantly, it greatly extends the C++ standard library and standard template library (STL).
C++ Features
  1. C++ is C. C++ supports (almost) all the features of C. Like C, C++ allows programmers to manage the memory directly, so as to develop efficient programs.
  2. C++ is OO. C++ enhances the procedural-oriented C language with the object-oriented extension. The OO extension facilitates design, reuse and maintenance for complex software.
  3. Template C++. C++ introduces generic programming, via the so-called template. You can apply the same algorithm to different data types.
  4. STL. C++ provides a huge set of reusable standard libraries, in particular, the Standard Template Library (STL).
C++ Strength and Pitfall

C++ is a powerful language for high-performance applications, including writing operating systems and their subsystems, games and animation. C++ is also a complex and difficult programming language, which is really not meant for dummies. For example, to effectively use the C++ Standard Template Library (STL), you need to understand these difficult concepts: pointers, references, operator overloading and template, on top of the object-oriented programming concepts such as classes and objects, inheritance and polymorphism; and the traditional constructs such as decision and loop. C++ is performance centric. The C++ compiler does not issue warning/error message for many obvious programming mistakes, undefined and unspecified behaviors, such as array index out of range, using an uninitialized variable, etc, due to the focus on performance and efficiency rather than the ease of use - it assumes that those who choose to program in C++ are not dummies.

Basic Syntaxes

Revision

Below is a simple C++ program that illustrates the important programming constructs (sequential flow, while-loop, and if-else) and input/output. Read "Introduction To C++ Programming for Novices and First-time Programmers" if you need help in understanding this program.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> using namespace std; int main() { int sumOdd = 0; int sumEven = 0; int upperbound; int absDiff; cout << "Enter the upperbound: "; cin >> upperbound; int number = 1; while (number <= upperbound) { if (number % 2 == 0) { sumEven += number; } else { sumOdd += number; } ++number; } if (sumOdd > sumEven) { absDiff = sumOdd - sumEven; } else { absDiff = sumEven - sumOdd; } cout << "The sum of odd numbers is " << sumOdd << endl; cout << "The sum of even numbers is " << sumEven << endl; cout << "The absolute difference is " << absDiff << endl; return 0; }
Enter the upperbound: 1000 The sum of odd numbers is 250000 The sum of even numbers is 250500 The absolute difference is 500
Program Notes

using namespace std;
The names and belong to the namespace. They can be referenced via fully qualified name and , or simply as and with a " statement. For simplicity, I shall use the latter approach in this section. I will discuss the significance later.

return 0;
The return value of 0 indicates normal termination; while non-zero (typically 1) indicates abnormal termination. C++ compiler will automatically insert a "" at the end of the the function, thus, it statement can be omitted.

Instead of using numeric value of zero and non-zero, you can also use or , which is defined in the header (i.e., you need to "".

Comments

Comments are used to document and explain your codes and program logic. Comments are not programming statements and are ignored by the compiler, but they VERY IMPORTANT for providing documentation and explanation for others to understand your program (and also for yourself three days later).

There are two kinds of comments in C/C++:

  1. Multi-line Comment: begins with a and ends with a , and can span several lines.
  2. End-of-line Comment: begins with and lasts till the end of the current line.

You should use comments liberally to explain and document your codes. During program development, instead of deleting a chunk of statements permanently, you could comment-out these statements so that you could get them back later, if needed.

Statements and Blocks

Statement: A programming statement is the smallest independent unit in a program, just like a sentence in the English language. It performs a piece of programming action. A programming statement must be terminated by a semi-colon (), just like an English sentence ends with a period. (Why not ends with a period like an english sentence? This is because period crashes with decimal point - it is hard for the dumb computer to differentiate between period and decimal point!)

For examples,

int number1 = 10; int number2, number3 = 99; int product; product = number1 * number2 * number3; cout << "Hello" << endl;

Block: A block (or a compound statement) is a group of statements surrounded by braces . All the statements inside the block is treated as one unit. Blocks are used as the body in constructs like function, if-else and loop, which may contain multiple statements but are treated as one unit. There is no need to put a semi-colon after the closing brace to end a complex statement. Empty block (without any statement) is permitted. For examples,

if (mark >= 50) { cout << "PASS" << endl; cout << "Well Done!" << endl; cout << "Keep it Up!" << endl; } if (number == 88) { cout << "Got it" << endl; } else { cout << "Try Again" << endl; } i = 1; while (i < 8) { cout << i << endl; ++i; } int main() { ...statements... }

White Spaces and Formatting Source Codes

White Spaces: Blank, tab and new-line are collectively called white spaces. C++, like most of the computing languages, ignores extra white spaces. That is, multiple contiguous white spaces are treated as a single white space.

You need to use a white space to separate two keywords or tokens, e.g.,

int sum=0; double average; average=sum/100.0;

Additional white spaces and extra lines are, however, ignored, e.g.,

int sum = 0 ; double average ; average = sum / 100.0;

Formatting Source Codes: As mentioned, extra white spaces are ignored and have no computational significance. However, proper indentation (with tabs and blanks) and extra empty lines greatly improves the readability of the program, which is extremely important for others (and yourself three days later) to understand your programs. For example, the following hello-world works, but can you understand the program?

#include <iostream> using namespace std;int main(){cout<<"Hello, world!"<<endl;return 0;}

Braces: Place the beginning brace at the end of the line, and align the ending brace with the start of the statement.

Indentation: Indent the body of a block by an extra 3 (or 4 spaces), according to its level.

For example,

#include <iostream> using namespace std; int main() { int mark = 70; if (mark >= 50) { cout << "You Pass!" << endl; } else { cout << "You Fail!" << endl; } return 0; }

Most IDEs (such as CodeBlocks, Eclipse and NetBeans) have a command to re-format your source code automatically.

Note: Traditional C-style formatting places the beginning and ending braces on the same column. For example,

#include <iostream> using namespace std; int main() { if (mark >= 50) { cout << "You Pass!" << endl; } else { cout << "You Fail!" << endl; }}

Preprocessor Directives

C++ source code is pre-processed before it is compiled into object code (as illustrated).

A preprocessor directive, which begins with a sign (such as , ), tells the preprocessor to perform a certain action (such as including a header file, or performing text replacement), before compiling the source code into object code. Preprocessor directives are not programming statements, and therefore should NOT be terminated with a semi-colon. For example,

#include <iostream> #include <cmath> #define PI 3.14159265 // DO NOT terminate preprocessor directive with a semi-colon

In almost all of the C++ programs, we use to include the input/output stream library header into our program, so as to use the IO library function to carry out input/output operations (such as and ).

More on preprocessor directives later.

Variables and Types

Variables

Computer programs manipulate (or process) data. A variable is used to store a piece of data for processing. It is called variable because you can change the value stored.

More precisely, a variable is a named storage location, that stores a value of a particular data type. In other words, a variable has a name, a type and stores a value.

  • A variable has a name (or identifier), e.g., , , , . The name is needed to uniquely identify each variable, so as to assign a value to the variable (e.g., ), and retrieve the value stored (e.g., ).
  • A variable has a type. Examples of type are,
    • : for integers (whole numbers) such as and ;
    • : for floating-point or real numbers such as , , having a decimal point and fractional part.
  • A variable can store a value of that particular type. It is important to take note that a variable in most programming languages is associated with a type, and can only store value of the particular type. For example, a variable can store an integer value such as , but NOT real number such as , nor texts such as .
  • The concept of type was introduced into the early programming languages to simplify interpretation of data made up of 0s and 1s. The type determines the size and layout of the data, the range of its values, and the set of operations that can be applied.

The following diagram illustrates two types of variables: and . An variable stores an integer (whole number). A variable stores a real number.

Identifiers

An identifier is needed to name a variable (or any other entity such as a function or a class). C++ imposes the following rules on identifiers:

  • An identifier is a sequence of characters, of up to a certain length (compiler-dependent, typically 255 characters), comprising uppercase and lowercase letters , digits , and underscore .
  • White space (blank, tab, new-line) and other special characters (such as , , , , , , commas, etc.) are not allowed.
  • An identifier must begin with a letter or underscore. It cannot begin with a digit. Identifiers beginning with an underscore are typically reserved for system use.
  • An identifier cannot be a reserved keyword or a reserved literal (e.g.,, , , , ).
  • Identifiers are case-sensitive. A is NOT a , and is NOT a .

Caution: Programmers don't use blank character in names. It is either not supported, or will pose you more challenges.

Variable Naming Convention

A variable name is a noun, or a noun phrase made up of several words. The first word is in lowercase, while the remaining words are initial-capitalized, with no spaces between words. For example, , , , , and . This convention is also known as camel-case.

Recommendations
  1. It is important to choose a name that is self-descriptive and closely reflects the meaning of the variable, e.g., or .
  2. Do not use meaningless names like , , , , , , , , .
  3. Avoid single-alphabet names, which is easier to type but often meaningless, unless they are common names like , , for coordinates, for index.
  4. It is perfectly okay to use long names of says 30 characters to make sure that the name accurately reflects its meaning!
  5. Use singular and plural nouns prudently to differentiate between singular and plural variables.  For example, you may use the variable to refer to a single row number and the variable to refer to many rows (such as an array of rows - to be discussed later).

Variable Declaration

To use a variable in your program, you need to first "introduce" it by declaring its name and type, in one of the following syntaxes:

SyntaxExample
type identifier;type identifier-1,identifier-2, ...,identifier-n;type identifier=value;type identifier-1=value-1, ...,identifier-n=value-n;  int option;   double sum, difference, product, quotient;   int magicNumber = 88;   double sum = 0.0, product = 1.0;

Example,

int mark1; mark1 = 76; int mark2; mark2 = mark1 + 10; double average; average = (mark1 + mark2) / 2.0; int mark1;mark2 = "Hello";

Take note that:

  • In C++, you need to declare the name of a variable before it can be used.
  • C++ is a "strongly-type" language. A variable takes on a type. Once the type of a variable is declared, it can only store a value belonging to this particular type. For example, an variable can hold only integer such as , and NOT floating-point number such as or text string such as . The concept of type was introduced into the early programming languages to simplify interpretation of data made up of 0s and 1s. Knowing the type of a piece of data greatly simplifies its interpretation and processing.
  • Each variable can only be declared once.
  • In C++, you can declare a variable anywhere inside the program, as long as it is declared before used. (In C prior to C99, all the variables must be declared at the beginning of functions.) It is recommended that your declare a variable just before it is first used.
  • The type of a variable cannot be changed inside the program.
CAUTION: Uninitialized Variables

When a variable is declared, it contains garbage until you assign an initial value. It is important to take note that C/C++ does not issue any warning/error if you use a variable before initialize it - which certainly leads to some unexpected results. For example,

1 2 3 4 5 6 7 8 9 #include <iostream> using namespace std; int main() { int number; cout << number << endl; return 0; }

Constants (const)

Constants are non-modifiable variables, declared with keyword . Their values cannot be changed during program execution. Also, must be initialized during declaration. For examples:

const double PI = 3.1415926;

Constant Naming Convention: Use uppercase words, joined with underscore. For example, , .

Expressions

An expression is a combination of operators (such as addition , subtraction , multiplication , division ) and operands (variables or literal values), that can be evaluated to yield a single value of a certain type. For example,

1 + 2 * 3 int sum, number; sum + number double principal, interestRate; principal * (1 + interestRate)

Assignment (=)

An assignment statement:

  1. assigns a literal value (of the RHS) to a variable (of the LHS); or
  2. evaluates an expression (of the RHS) and assign the resultant value to a variable (of the LHS).

The RHS shall be a value; and the LHS shall be a variable (or memory address).

The syntax for assignment statement is:

SyntaxExample
variable=literal-value;variable=expression;  number = 88;   sum = sum + number;

The assignment statement should be interpreted this way: The expression on the right-hand-side (RHS) is first evaluated to produce a resultant value (called rvalue or right-value). The rvalue is then assigned to the variable on the left-hand-side (LHS) (or lvalue, which is a location that can hold a rvalue). Take note that you have to first evaluate the RHS, before assigning the resultant value to the LHS. For examples,

number = 8; number = number + 1;

The symbol "" is known as the assignment operator. The meaning of "" in programming is different from Mathematics. It denotes assignment instead of equality. The RHS is a literal value; or an expression that evaluates to a value; while the LHS must be a variable. Note that is valid (and often used) in programming. It evaluates and assign the resultant value to the variable . illegal in Mathematics. While is allowed in Mathematics, it is invalid in programming (because the LHS of an assignment statement must be a variable). Some programming languages use symbol "", "←", "->", or "→" as the assignment operator to avoid confusion with equality.

Fundamental Types

Integers: C++ supports these integer types: , , , , (in C++11) in a non-decreasing order of size. The actual size depends on the implementation. The integers (except ) are signed number (which can hold zero, positive and negative numbers). You could use the keyword to declare an unsigned integers (which can hold zero and positive numbers). There are a total 10 types of integers - combined with .

Characters: Characters (e.g., , , , ) are encoded in ASCII into integers, and kept in type . For example, character is (decimal) or (hexadecimal); character is (decimal) or (hexadecimal); character is (decimal) or (hexadecimal). Take note that the type can be interpreted as character in ASCII code, or an 8-bit integer. Unlike or , which is , could be or , depending on the implementation. You can use or to explicitly declare or .

Floating-point Numbers: There are 3 floating point types: , and , for single, double and long double precision floating point numbers. and are represented as specified by IEEE 754 standard. A can represent a number between and , approximated. A can represented a number between and , approximated. Take note that not all real numbers can be represented by and , because there are infinite real numbers. Most of the values are approximated.

Boolean Numbers: A special type called (for boolean), which takes a value of either or .

The table below shows the typical size, minimum, maximum for the primitive types. Again, take note that the sizes are implementation dependent.

CategoryTypeDescriptionBytes
(Typical)
Minimum
(Typical)
Maximum
(Typical)
Integersint
(or signed int)
Signed integer (of at least 16 bits)4 (2)-21474836482147483647
unsigned intUnsigned integer (of at least 16 bits)4 (2)04294967295
charCharacter
(can be either signed or unsigned depends on implementation)
1  
signed charCharacter or signed tiny integer
(guarantee to be signed)
1-128127
unsigned charCharacter or unsigned tiny integer
(guarantee to be unsigned)
10255
short
(or short int)
(or signed short)
(or signed short int)
Short signed integer (of at least 16 bits)2-3276832767
unsigned short
(or unsigned shot int)
Unsigned short integer (of at least 16 bits)2065535
long
(or long int)
(or signed long)
(or signed long int)
Long signed integer (of at least 32 bits)4 (8)-21474836482147483647
unsigned long
(or unsigned long int)
Unsigned long integer (of at least 32 bits)4 (8)0same as above
long long
(or long long int)
(or signed long long)
(or signed long long int) (C++11)
Very long signed integer (of at least 64 bits)8-263263-1
unsigned long long
(or unsigned long long int) (C++11)
Unsigned very long integer (of at least 64 bits)80264-1
Real NumbersfloatFloating-point number, ≈7 digits
(IEEE 754 single-precision floating point format)
43.4e383.4e-38
doubleDouble precision floating-point number, ≈15 digits
(IEEE 754 double-precision floating point format)
81.7e3081.7e-308
long doubleLong double precision floating-point number, ≈19 digits
(IEEE 754 quadruple-precision floating point format)
12 (8)  
Boolean
Numbers
boolBoolean value of either or 1false (0)true (1 or non-zero)
Wide
Characters
wchar_t
char16_t (C++11)
char32_t (C++11)
Wide (double-byte) character2 (4)  

In addition, many C++ library functions use a type called , which is equivalent () to a , meant for counting, size or length, with 0 and positive integers.

*The sizeof Operator

C/C++ provides an unary operator to get the size of the operand (in bytes). The following program uses operator to print the size of the fundamental types.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std; int main() { cout << "sizeof(char) is " << sizeof(char) << " bytes " << endl; cout << "sizeof(short) is " << sizeof(short) << " bytes " << endl; cout << "sizeof(int) is " << sizeof(int) << " bytes " << endl; cout << "sizeof(long) is " << sizeof(long) << " bytes " << endl; cout << "sizeof(long long) is " << sizeof(long long) << " bytes " << endl; cout << "sizeof(float) is " << sizeof(float) << " bytes " << endl; cout << "sizeof(double) is " << sizeof(double) << " bytes " << endl; cout << "sizeof(long double) is " << sizeof(long double) << " bytes " << endl; cout << "sizeof(bool) is " << sizeof(bool) << " bytes " << endl; return 0; }
sizeof(char) is 1 bytes sizeof(short) is 2 bytes sizeof(int) is 4 bytes sizeof(long) is 4 bytes sizeof(long long) is 8 bytes sizeof(float) is 4 bytes sizeof(double) is 8 bytes sizeof(long double) is 12 bytes sizeof(bool) is 1 bytes

The results may vary among different systems.

*Header <climits>

The header (ported to C++ from C's ) contains information about limits of integer type. For example,

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <climits> using namespace std; int main() { cout << "int max = " << INT_MAX << endl; cout << "int min = " << INT_MIN << endl; cout << "unsigned int max = " << UINT_MAX << endl; cout << "long long max = " << LLONG_MAX << endl; cout << "long long min = " << LLONG_MIN << endl; cout << "unsigned long long max = " << ULLONG_MAX << endl; cout << "Bits in char = " << CHAR_BIT << endl; cout << "char max = " << CHAR_MAX << endl; cout << "char min = " << CHAR_MIN << endl; cout << "signed char max = " << SCHAR_MAX << endl; cout << "signed char min = " << SCHAR_MIN << endl; cout << "unsigned char max = " << UCHAR_MAX << endl; return 0; }
int max = 2147483647 int min = -2147483648 unsigned int max = 4294967295 long long max = 9223372036854775807 long long min = -9223372036854775808 unsigned long long max = 18446744073709551615 Bits in char = 8 char max = 127 char min = -128 signed char max = 127 signed char min = -128 unsigned char max = 255

Again, the outputs depend on the system.

The minimum of unsigned integer is always 0. The other constants are , , , , , . Try inspecting this header (search for under your compiler).

*Header <cfloat>

Similarly, the header (ported from C's ) contain information on limits for floating point numbers, such as minimum number of significant digits (, , for , and ), number of bits for mantissa (, , ), maximum and minimum exponent values, etc. Try inspecting this header (search for under your compiler).

*Header <limits>

The and headers are ported over from C's and . C++ added a new header called .

[TODO]

Choosing Types

As a programmer, you need to choose variables and decide on the type of the variables to be used in your programs. Most of the times, the decision is intuitive. For example, use an integer type for counting and whole number; a floating-point type for number with fractional part, for a single character, and for binary outcome.

Rule of Thumb
  • Use for integer and for floating point numbers. Use , , and only if you have a good reason to choose that specific precision.
  • Use (or ) for counting and indexing, NOT floating-point type ( or ). This is because integer type are precise and more efficient in operations.
  • Use an integer type if possible. Use a floating-point type only if the number contains a fractional part.

Read my article on "Data Representation" if you wish to understand how the numbers and characters are represented inside the computer memory. In brief, It is important to take note that is different from , , , , and . They are represented differently in the computer memory, with different precision and interpretation. For example, is , is , is , is , is , is .

There is a subtle difference between and .

Furthermore, you MUST know the type of a value before you can interpret a value. For example, this value cannot be interpreted unless you know the type.

*The typedef Statement

Typing "" many time can get annoying. The statement can be used to create a new name for an existing type. For example, you can create a new type called "" for "" as follow. You should place the immediately after . Use with care because it makes the program hard to read and understand.

typedef unsigned int uint;

Many C/C++ compilers define a type called , which is a of .

typedef unsigned int size_t;

Literals for Fundamental Types and String

A literal is a specific constant value, such as , , , , , that can be assigned directly to a variable; or used as part of an expression. They are called literals because they literally and explicitly identify their values.

Integer Literals

A whole number, such as and , is treated as an , by default. For example,

int number = -123; int sum = 4567; int bigSum = 8234567890;

An literal may precede with a plus () or minus () sign, followed by digits. No commas or special symbols (e.g., or space) is allowed (e.g., and are invalid). No preceding is allowed too (e.g., is invalid).

Besides the default base 10 integers, you can use a prefix (zero) to denote a value in octal, prefix for a value in hexadecimal, and prefix '' for binary value (in some compilers), e.g.,

int number1 = 1234; int number2 = 01234; int number3 = 0x1abc; int number4 = 0b10001001;

A literal is identified by a suffix or (avoid lowercase, which can be confused with the number one). A is identified by a suffix . You can also use suffix for , for , and for . For example,

long number = 12345678L; long sum = 123; long long bigNumber = 987654321LL;

No suffix is needed for literals. But you can only use integer values in the permitted range. For example,

short smallNumber = 1234567890; short midSizeNumber = -12345;
Floating-point Literals

A number with a decimal point, such as and , is treated as a , by default. You can also express them in scientific notation, e.g., , , where or denotes the exponent in power of 10. You could precede the fractional part or exponent with a plus () or minus () sign. Exponent shall be an integer. There should be no space or other characters (e.g., space) in the number.

You MUST use a suffix of or for literals, e.g., . For example,

float average = 55.66; float average = 55.66f;

Use suffix (or ) for .

Character Literals and Escape Sequences

A printable literal is written by enclosing the character with a pair of single quotes, e.g., , , and . In C++, characters are represented using 8-bit ASCII code, and can be treated as a 8-bit signed integers in arithmetic operations. In other words, and 8-bit signed integer are interchangeable. You can also assign an integer in the range of to a variable; and to an .

You can find the ASCII code table HERE.

For example,

char letter = 'a'; char anotherLetter = 98; cout << letter << endl; cout << anotherLetter << endl; anotherLetter += 2; cout << anotherLetter << endl; cout << (int)anotherLetter << endl;

Non-printable and control characters can be represented by a so-called escape sequence, which begins with a back-slash (). The commonly-used escape sequences are:

Escape SequenceDescriptionHex (Decimal)
\nNew-line (or Line-feed)0AH (10D)
\rCarriage-return0DH (13D)
\tTab09H (9D)
\"Double-quote (needed to include " in double-quoted string)22H (34D)
\'Single-quote27H (39D)
\\Back-slash (to resolve ambiguity)5CH (92D)

Notes:

  • New-line () and carriage return (), represented by , and respectively, are used as line delimiter (or end-of-line, or EOL). However, take note that Unixes/Mac use as EOL, Windows use .
  • Horizontal Tab () is represented as .
  • To resolve ambiguity, characters back-slash (), single-quote () and double-quote () are represented using escape sequences , and , respectively. This is because a single back-slash begins an escape sequence, while single-quotes and double-quotes are used to enclose character and string.
  • Other less commonly-used escape sequences are: or , for alert or bell, for backspace, for form-feed, for vertical tab. These may not be supported in some consoles.
The <cctype> Header

The header (ported from C's ) provides functions such as , , , , , , to determine the type of character; and , for case conversion.

String Literals

A literal is composed of zero of more characters surrounded by a pair of double quotes, e.g., , , . For example,

String directionMsg = "Turn Right"; String greetingMsg = "Hello"; String statusMsg = "";

String literals may contains escape sequences. Inside a , you need to use for double-quote to distinguish it from the ending double-quote, e.g. . Single quote inside a does not require escape sequence. For example,

cout << "Use \\\" to place\n a \" within\ta\tstring" << endl; Use \" to place a " within a string

TRY: Write a program to print the following picture. Take note that you need to use escape sequences to print special characters.

'__' (oo) +========\/ / || %%% || * ||-----|| "" ""
bool Literals

There are only two literals, i.e., and . For example,

bool done = true; bool gameOver = false; int i; if (i == 9) { ...... }

In an expression, values and literals are converted to 0 for and 1 (or a non-zero value) for .

Example (Literals)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std; int main() { char gender = 'm'; bool isMarried = true; unsigned short numChildren = 8; short yearOfBirth = 1945; unsigned int salary = 88000; double weight = 88.88; float gpa = 3.88f; cout << "Gender is " << gender << endl; cout << "Is married is " << isMarried << endl; cout << "Number of children is " << numChildren << endl; cout << "Year of birth is " << yearOfBirth << endl; cout << "Salary is " << salary << endl; cout << "Weight is " << weight << endl; cout << "GPA is " << gpa << endl; return 0; }
Gender is m Is married is 1 Number of children is 8 Year of birth is 1945 Salary is 88000 Weight is 88.88 GPA is 3.88

Operations

Arithmetic Operators

C++ supports the following arithmetic operators for numbers: , , , , (treated as 8-bit signed integer), , , , , , , and .

OperatorDescriptionUsageExamples
*Multiplicationexpr1 * expr22 * 3 → 6; 3.3 * 1.0 → 3.3
/Divisionexpr1 / expr21 / 2 → 0; 1.0 / 2.0 → 0.5
%Remainder (Modulus)expr1 % expr25 % 2 → 1; -5 % 2 → -1
+Additionexpr1 + expr21 + 2 → 3; 1.1 + 2.2 → 3.3
-Subtractionexpr1 - expr21 - 2 → -1; 1.1 - 2.2 → -1.1

All the above operators are binary operators, i.e., they take two operands. The multiplication, division and remainder take precedence over addition and subtraction. Within the same precedence level (e.g., addition and subtraction), the expression is evaluated from left to right. For example, is evaluated as .

It is important to take note that produces an , with the result truncated, e.g.,(instead of ).

Take note that C/C++ does not have an exponent (power) operator ( is exclusive-or, not exponent).

Arithmetic Expressions

In programming, the following arithmetic expression:

must be written as . You cannot omit the multiplication symbol (as in Mathematics).

Like Mathematics, the multiplication and division take precedence over addition and subtraction . Parentheses have higher precedence. The operators , , , and are left-associative. That is, is treated as .

Mixed-Type Operations

If both the operands of an arithmetic operation belong to the same type, the operation is carried out in that type, and the result belongs to that type. For example, .

However, if the two operands belong to different types, the compiler promotes the value of the smaller type to the larger type (known as implicit type-casting). The operation is then carried out in the larger type. For example, . Hence, .

For example,

TypeExampleOperation
int2 + 3int 2 + int 3 → int 5
double2.2 + 3.3double 2.2 + double 3.3 → double 5.5
mix2 + 3.3int 2 + double 3.3 → double 2.0 + double 3.3 → double 5.3
int1 / 2int 1 / int 2 → int 0
double1.0 / 2.0double 1.0 / double 2.0 → double 0.5
mix1 / 2.0int 1 / double 2.0 → double 1.0 + double 2.0 → double 0.5
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <iomanip> using namespace std; int main() { int i1 = 2, i2 = 4; double d1 = 2.5, d2 = 5.0; cout << fixed << setprecision(1); cout << i1 << " + " << i2 << " = " << i1+i2 << endl; cout << d1 << " + " << d2 << " = " << d1+d2 << endl; cout << i1 << " + " << d2 << " = " << i1+d2 << endl; cout << i1 << " - " << i2 << " = " << i1-i2 << endl; cout << d1 << " - " << d2 << " = " << d1-d2 << endl; cout << i1 << " - " << d2 << " = " << i1-d2 << endl; cout << i1 << " * " << i2 << " = " << i1*i2 << endl; cout << d1 << " * " << d2 << " = " << d1*d2 << endl; cout << i1 << " * " << d2 << " = " << i1*d2 << endl; cout << i1 << " / " << i2 << " = " << i1/i2 << endl; cout << d1 << " / " << d2 << " = " << d1/d2 << endl; cout << i1 << " / " << d2 << " = " << i1/d2 << endl; return 0; }

Overflow/UnderFlow

Study the output of the following program:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std; int main() { int i1 = 2147483647; cout << i1 + 1 << endl; cout << i1 + 2 << endl; cout << i1 * i1 << endl; int i2 = -2147483648; cout << i2 - 1 << endl; cout << i2 - 2 << endl; cout << i2 * i2 << endl; return 0; }

In arithmetic operations, the resultant value wraps around if it exceeds its range (i.e., overflow or underflow). C++ runtime does not issue an error/warning message but produces incorrect result.

It is important to take note that checking of overflow/underflow is the programmer's responsibility, i.e., your job!

This feature is an legacy design, where processors were slow. Checking for overflow/underflow consumes computation power and reduces performance.

To check for arithmetic overflow (known as secure coding) is tedious. Google for "INT32-C. Ensure that operations on signed integers do not result in overflow" @ www.securecoding.cert.org.

Compound Assignment Operators

Besides the usual simple assignment operator described earlier, C++ also provides the so-called compound assignment operators as listed:

OperatorUsageDescriptionExample
=var = exprAssign the value of the LHS to the variable at the RHSx = 5;
+=var += exprsame as var = var + exprx += 5; same as x = x + 5
-=var -= exprsame as var = var - exprx -= 5; same as x = x - 5
*=var *= exprsame as var = var * exprx *= 5; same as x = x * 5
/=var /= exprsame as var = var / exprx /= 5; same as x = x / 5
%=var %= exprsame as var = var % exprx %= 5; same as x = x % 5

Increment/Decrement Operators

C++ supports these unary arithmetic operators: increment and decrement .

OperatorExampleResult
++x++; ++xIncrement by 1, same as x += 1
--x--; --xDecrement by 1, same as x -= 1

For example,

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std; int main() { int mark = 76; cout << mark << endl; mark++; cout << mark << endl; ++mark; cout << mark << endl; mark = mark + 1; cout << mark << endl; mark--; cout << mark << endl; --mark; cout << mark << endl; mark = mark - 1; cout << mark << endl; return 0; }

The increment/decrement unary operator can be placed before the operand (prefix operator), or after the operands (postfix operator). They takes on different meaning in operations.

OperatorDescriptionExampleResult
++varPre-Increment
Increment var, then use the new value of var
y = ++x;same as x=x+1; y=x;
var++Post-Increment
Use the old value of var, then increment var
y = x++;same as oldX=x; x=x+1; y=oldX;
--varPre-Decrementy = --x;same as x=x-1; y=x;
var--Post-Decrementy = x--;same as oldX=x; x=x-1; y=oldX;

If '++' or '--' involves another operation, then pre- or post-order is important to specify the order of the two operations. For examples,

x = 5; cout << x++ << endl; x = 5; cout << ++x << endl;

Prefix operator (e.g, ) could be more efficient than postfix operator (e.g., ) in some situations.

Implicit Type-Conversion vs. Explicit Type-Casting

Converting a value from one type to another type is called type casting (or type conversion). There are two kinds of type casting:

  1. Implicit type-conversion performed by the compiler automatically, and
  2. Explicit type-casting via an unary type-casting operator in the form of or .
Implicit (Automatic) Type Conversion

When you assign a value of a fundamental (built-in) type to a variable of another fundamental type, C++ automatically converts the value to the receiving type, if the two types are compatible. For examples,

  • If you assign an value to a variable, the compiler automatically casts the value to a double (e.g., from 1 to 1.0) and assigns it to the variable.
  • if you assign a value of to an variable, the compiler automatically casts the value to an value (e.g., from 1.2 to 1) and assigns it to the variable. The fractional part would be truncated and lost. Some compilers issue a warning/error "possible loss in precision"; others do not.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <iomanip> using namespace std; int main() { int i; double d; cout << fixed << setprecision(1); i = 3; d = i; cout << "d = " << d << endl; d = 5.5; i = d; cout << "i = " << i << endl; i = 6.6; cout << "i = " << i << endl; }

C++ will not perform automatic type conversion, if the two types are not compatible.

Explicit Type-Casting

You can explicitly perform type-casting via the so-called unary type-casting operator in the form of or . The type-casting operator takes one operand in the particular type, and returns an equivalent value in the new type. Take note that it is an operation that yields a resultant value, similar to an addition operation although addition involves two operands. For example,

cout << fixed << setprecision(1); cout << (double)5 << endl; cout << (int)5.5 << endl; double aDouble = 5.6; int anInt = (int)aDouble; cout << double(5) << endl; cout << int(5.5) << endl; cout << int(aDouble) << endl;

Example: Suppose that you want to find the average (in ) of the integers between and . Study the following codes:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <iomanip> using namespace std; int main() { int sum = 0; double average; for (int number = 1; number <= 100; ++number) { sum += number; } average = sum / 100; cout << fixed << setprecision(1); cout << "Average is " << average << endl; return 0; }

You don't get the fractional part although the is a . This is because both the and are . The result of division is an , which is then implicitly casted to and assign to the variable . To get the correct answer, you can do either:

average = (double)sum / 100; average = sum / (double)100; average = sum / 100.0; average = (double)(sum / 100); average = double(sum) / 100;

Example:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <iomanip> using namespace std; int main() { cout << fixed << setprecision(1); int i1 = 4, i2 = 8; cout << i1 / i2 << endl; cout << (double)i1 / i2 << endl; cout << i1 / (double)i2 << endl; cout << (double)(i1 / i2) << endl; double d1 = 5.5, d2 = 6.6; cout << (int)d1 / i2 << endl; cout << (int)(d1 / i2) << endl; d1 = i1; cout << d1 << endl; i2 = d2; cout << i2 << endl; }

Example:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <iomanip> using namespace std; int main() { double celsius, fahrenheit; cout << fixed << setprecision(2); cout << "Enter the temperature in celsius: "; cin >> celsius; fahrenheit = celsius*9/5 + 32; cout << celsius << "C is " << fahrenheit << "F" << endl; cout << "Enter the temperature in fahrenheit: "; cin >> fahrenheit; celsius = (fahrenheit - 32)*5/9; cout << fahrenheit << "F is " << celsius << "C" << endl; return 0; }
*Operator static-cast<type>

C++ introduces a new operator called to perform type conversion (because the regular cast mentioned earlier is too lax and could produce expected results). signal an error if conversion fails. For example,

double d = 5.5; int i = static_cast<int>(d); float f = static_cast<float>(i); long l = static_cast<logn>(d);

Relational and Logical Operators

Very often, you need to compare two values before deciding on the action to be taken, e.g., if mark is more than or equal to 50, print "PASS".

C++ provides six comparison operators (or relational operators):

OperatorDescriptionUsageExample (x=5, y=8)
==Equal toexpr1 == expr2(x == y) → false
!=Not Equal toexpr1 != expr2(x != y) → true
>Greater thanexpr1 > expr2(x > y) → false
>=Greater than or equal toexpr1 >= expr2(x >= 5) → true
<Less thanexpr1 < expr2(y < 8) → false
<=Less than or equal toexpr1 >= expr2(y <= 8) → true

In C++, these comparison operations returns a value of either (0) or (1 or a non-zero value).

Each comparison operation involves two operands, e.g., . It is invalid to write in programming. Instead, you need to break out the two comparison operations , , and join with with a logical AND operator, i.e., , where denotes AND operator.

C++ provides four logical operators (which operate on operands only):

OperatorDescriptionUsage
&&Logical ANDexpr1 && expr2
||Logical ORexpr1 || expr2
!Logical NOT!expr
^Logical XORexpr1 ^ expr2

The truth tables are as follows:

AND (&&)truefalse
true
false
OR (||)truefalse
true
false
XOR (^)truefalse
true
false

Example:

(x >= 0) && (x <= 100)   (x < 0) || (x > 100) !((x >= 0) && (x <= 100))   ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)

Exercise: Given the year, month (1-12), and day (1-31), write a boolean expression which returns true for dates before October 15, 1582 (Gregorian calendar cut over date).

Ans:

Flow Control

There are three basic flow control constructs - sequential, conditional (or decision), and loop (or iteration), as illustrated below.

Sequential Flow Control

A program is a sequence of instructions. Sequential flow is the most common and straight-forward, where programming statements are executed in the order that they are written - from top to bottom in a sequential manner.

Conditional (Decision) Flow Control

There are a few types of conditionals, if-then, if-then-else, nested-if (if-elseif-elseif-...-else), switch-case, and conditional expression.

"switch-case" is an alternative to the "nested-if". In a switch-case statement, a statement is needed for each of the cases. If is missing, execution will flow through the following case. You can use either an or variable as the case-selector.

Conditional Operator: A conditional operator is a ternary (3-operand) operator, in the form of . Depending on the , it evaluates and returns the value of or .

SyntaxExample
booleanExpr?trueExpr:falseExprcout << (mark >= 50) ? "PASS" : "FAIL" << endl; max = (a > b) ? a : b; abs = (a > 0) ? a : -a;

Braces: You could omit the braces , if there is only one statement inside the block. For example,

if (mark >= 50) cout << "PASS" << endl; else { cout << "FAIL" << endl; cout << "Try Harder!" << endl; }

However, I recommend that you keep the braces, even though there is only one statement in the block, to improve the readability of your program.

Exercises

[TODO]

Loop Flow Control

Again, there are a few types of loops: for-loop, while-do, and do-while.

SyntaxExampleFlowchart
for (init;test;post-proc) {body;} int sum = 0; for (int number = 1; number <= 1000; ++number) { sum += number; }
while (condition) {body; }  int sum = 0, number = 1; while (number <= 1000) { sum += number; ++number; }
do {body; } while (condition) ;int sum = 0, number = 1; do { sum += number; ++number; } while (number <= 1000);

The difference between while-do and do-while lies in the order of the body and condition. In while-do, the condition is tested first. The body will be executed if the condition is true and the process repeats. In do-while, the body is executed and then the condition is tested. Take note that the body of do-while will be executed at least once (vs. possibly zero for while-do).

Suppose that your program prompts user for a number between to , and checks for valid input, do-while with a boolean flag could be more appropriate.

bool valid = false; int number; do { ...... if (number >=1 && number <= 10) { valid = true; } } while (!valid);

Below is an example of using while-do:

bool gameOver = false; while (!gameOver) { ...... ...... }

Example (Counter-Controlled Loop): Prompt user for an upperbound. Sum the integers from 1 to a given upperbound and compute its average.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std; int main() { int sum = 0; int upperbound; cout << "Enter the upperbound: "; cin >> upperbound; for (int number = 1; number <= upperbound; ++number) { sum += number; } cout << "Sum is " << sum << endl; cout << "Average is " << (double)sum / upperbound << endl; int count = 0; sum = 0; for (int number=1; number <= upperbound; number=number+2) { ++count; sum += number; } cout << "Sum of odd numbers is " << sum << endl; cout << "Average is " << (double)sum / count << endl; }

Example (Sentinel-Controlled Loop): Prompt user for positive integers, and display the count, maximum, minimum and average. Terminate when user enters -1.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <climits> #include <iomanip> using namespace std; int main() { int numberIn; int count = 0; int sum = 0; int max = 0;

0 thoughts on “Overloading Bracket Operator C++ Assignment Answers”

    -->

Leave a Comment

Your email address will not be published. Required fields are marked *