Learning to be Giant.

C++ Learning Notes V


This is the 5th article in this series. It has been 10 days since the last article was published.

Sequential Containers

  1. forward_list and array forward_list is similar to list but with links both to the former node and the later node. array is similar to built-in array, whose size cannot be changed.

    cpp #include <array> int main() { std::array<int,3> arr = {1,2,3}; }

    cpp #include <forward_list> int main() { std::forward_list<int> lst = {1,2,3}; }

  2. emplace, emplace_back, emplace_front These “emplace” methods allow you to insert a element into the container by constructing a new element using the constructor.

    cpp typedef struct Task_t { Task_t(int a, int b) { // do something } } Task; // omitted some codes vector<Task> task_vec = {task1, task2, task3}; // task# are Task instances task_vec.emplace_back(1,2); // this method calls the constructor task_vec.push_back(Task(1,2)); // same to the above

  3. Converting Functions The new standard introduces some new converting functions that can convert strings to numbers and vice versa, just like atoi() in C. > to_string(val) Number to string > stoi(s, p, b) stol(s, p, b) stoul(s, p, b) stoll(s, p, b) stoull(s, p, b) stof(s, p) stod(s, p) stold(s, p) String to numbers


One important thing to know is that the generic algorithms are designed to be called in similar pattern. They always take 2 parameters(iterators) to indicate the range of elements. One for the beginning point, another for the ending point.

vector<int> v = {1,2,3,4,5};
auto result = find(begin(v), end(v), 2);

Notice that we didn’t use the v.begin() and v.end(). We used begin(v) and end(v) instead. begin() and end() are to overloaded functions provided by the new standard to get the beginning iterator and ending iterator of a container. It can also be used on built-in arrays. When used on containers, it performs the same behavior as the .begin() and .end().

accumulate function

This is a read-only function, which means that it will iteratively read through the container without writing or modifying.

int sum = accumulate(v.cbegin(), v.cend, 0);

It takes 3 arguments, the first two are range of the container, and the third is the initial value of the summation.

equal function

This is also a read-only function.

// roster2 should have at least as many elements as roster1 
equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());

Moreover, the element types also need not be the same so long as we can use == to compare the element types. For example, roster1 could be a vector and roster2 a list<const char*>.

*Due to some reasons, I cannot compile the some of the C++11 codes. *

fill function

This function will write the container in the range specified by the two iterators.

fill(vec.begin(), vec.end(), 0); // set all the elements between begin and end to be 0


Some functions will not check if the container has enough space to write in new elements. For example, the fill_n() function.

vector<int> v;
fill_n(v.begin(), 20, 0); // this is illegal, since the vector v does not have 20 spaces

What we can do is to replace the iterator to a back_inserter(). This function will return a back_inserter_iterator, which is defined as:

template <class Container>
  class back_insert_iterator :
    public iterator<output_iterator_tag,void,void,void,void>
  Container* container;

  typedef Container container_type;
  explicit back_insert_iterator (Container& x) : container(&x) {}
  back_insert_iterator<Container>& operator= (const typename Container::value_type& value)
    { container->push_back(value); return *this; }
  back_insert_iterator<Container>& operator= (typename Container::value_type&& value)
    { container->push_back(std::move(value)); return *this; }
  back_insert_iterator<Container>& operator* ()
    { return *this; }
  back_insert_iterator<Container>& operator++ ()
    { return *this; }
  back_insert_iterator<Container> operator++ (int)
    { return *this; }

As can be seen, it’s not like a typical iterator. The *,++ things are provided to satisfy the OutputIterator.

And we can rewrite the former code as:

fill_n(back_inserter(v.begin()), 20, 0); // ok

###unique function This is an interesting function.

Removes all consecutive duplicate elements from the container. Only the first element in each group of equal elements is left.

std::vector<string> v = {"hello","world","hello","yes","no","hello"};
sort(v.begin(), v.end());
auto iter = unique(v.begin(), v.end());
for (auto ele : v) {
    cout << ele << endl;
cout << v.size() << endl;

/* The out put is:
 * hello
 * no
 * world
 * yes
 * 6

You may have found that unique removes the duplicated elements, but the the element is not actually erased. We need to call the member function erase to clean the mess.

v.erase(iter, v.end());

Now, it performs as we expected.

####for_each Apart from the foreach syntax provided by the new standard, we have a for_each() in the generic algorithm library.

// print words of the given size or longer, each one followed by a space 
for_each(wc, words.end(),[](const string &s){cout << s << " ";});

##Lambda expression There are 3 kinds of callable object. They are: function and function pointers, function object(class that overloaded ()), and lambda expression.

Lambda expression can be thought to be a anonymous inline function.

 [capture list](parameter list) -> return type { function body }

where capture list is an (often empty) list of local variables defined in the enclosing function; return type, parameter list, and function body are the same as in any ordinary function. However, unlike ordinary functions, a lambda must use a trailing return to specify its return type.

We can omit either or both of the parameter list and return type but must al- ways include the capture list and function body.

auto f = []{return 42;}

As said in the quoted part, in capture list we can declare to use some local variables.

int test() {
    int a = 2;
    auto f = [a]{return a;};
    return f();

int main(int argc, char const *argv[])
    cout << test() << endl; // this will output 2


We can capture by value or by reference.

[a, c]{return a+c;}; // by value
[&a]{a++;}; // by reference

The capture part can also be done implicitly.

[=]{return a+c;}; // by value. this will implicitly capture a and c
[&]{a++;}; // by reference. this will implicitly capture a

some other ways:

Capture Description
[&,identifier_list] variables in identifier_list will be captured by value
[=,identifier_list] variables in identifier_list will be captured by reference

In fact, if we capture a variable by value, in a lambda expression, we can still change its value:

void fcn3() {
size_t v1 = 42; // local variable
// f can change the value of the variables it captures
auto f = [v1] () mutable { return ++v1; }; v1 = 0;
auto j = f(); // j is 43

in this way, the () after capture list cannot be omitted.

We didn’t try to specify a return type in the above cases because the return type can be inferred by the compiler. However, in some cases, the return type cannot be correctly inferred, and we need to manually specify the return type.

auto f = []() -> int {return 2;} // we specify the return type although the return type can be correctly inferred

##Misc. 1. bind The new standard provides a bind function as function adaptor. It takes a callable object and generates a new callable that adapts the parameter list of the original object.

auto newCallable = bind(callable, arg_list);

int func(int a, int b, intc);
auto f = bind(func, _2, _1, 2);
now if we call `f(1,2)`, it will call `func(2,1,2)`, since we bind the 2nd argument of the new callable to the 1st argument of the original `func`. the `_#` indicates a placeholder, the `#` represents the position of argument in the new callable.

I didn't plan to cover every single detail in the book. I only write down some notes that I used to misunderstand.