C++
In C++, a true bool
type is available with only true
and false
values.
In C, 'x'
and '\n'
are of type int
. In C++, they are of type char
.
Can decorate 1000000
as 1'000'000
.
C header file | C++ header file |
---|---|
assert.h | cassert |
ctype.h | cctype |
errno.h | cerrno |
float.h | cfloat |
limits.h | climits |
math.h | cmath |
stdarg.h | cstdarg |
stddef.h | cstddef |
stdint.h | cstdint |
stdio.h | cstdio |
stdlib.h | cstdlib |
string.h | cstring |
time.h | ctime |
Use C function in C++, if function is a common library function or was compiled by a c compiler.
// in.c
#include <stdio.h>
void my_printer(char *s){
printf("%s\n", s);
}
compile with
gcc -c in.c
// extern.cpp
#include <iostream>
extern "C" double sin(double);
extern "C"
{
double cos(double);
double tan(double);
void my_printer(char*);
}
int main(){
std::cout << sin(1.0) << std::endl;
std::cout << cos(1.0) << std::endl;
std::cout << tan(1.0) << std::endl;
char a[] = {'c', ' ', 'c', 'o', 'd', 'e'};
my_printer(a);
return 0;
}
compile with
g++ -o extern extern.cpp in.o
In C++ a function can be oveloaded: same name, different signatures and definitions.
If an overloaded function can handle int
and double
arguments, a compiler
error occurs if it is invoked on a long
value \(\rightarrow\) the compiler
can't know to which type it should cast the long
. Do an explicit cast.
Formal paramaters having default values should only be present in the function declaration. They should not be followed by formal parameters having no default values.
A namespace declaration can only contain declarations and no definitions, use extern for namespace variable to avoid defining it and breaking the One Definition Rule.
Extracting namespace component for use inside of a single compilation unit.
// ...
y = Math::power(x, 3)*Math::pi;
becomes
using Math::power;
using Math::pi;
// ...
y = power(x, 3)*pi;
Extracting all namespace components, USE ONLY IF A FEW NAMESPACES ARE USED.
using namespace Math;
Explicitly use a function in the globale namespace (ie: not declared in any namespace)
#include <cstdio>
int main(){
::printf("bruh\n");
return 0;
}
Anonymous Namespaces can only contain definitions (any declarations cannot be accessed bruh). These definitions can only be used in the current compilation unit.
namespace {
int f(n) {return n*n;}
}
has the same effect as
static int f(n) {return n*n;}
all references must be initialized
double x, y;
double &r = x; // r is a reference to the variable x
r = y; // the variable x now contains the value of the variable y
Never return a reference to a local variable or to a parameter not passed by reference (ie passed by value).
// OK
double& maximum(double& a, double& b){
return (a>=b)?a:b;
}
// STOOPID
double& maximum(double a, double b){
return (a>=b)?a:b;
}
A lot to unpack, but the common idiom is to declare a function receiving const references and returning a const reference, and an overloaded version which receives non const references and returns a non const reference.
double& maxi(double& a, double& b){
return (a>b) ? a : b;
}
const double& maxi(const double& a, const double& b){
return (a>b) ? a : b;
}
a reference must always be initialized or declared extern (initialized in an other compilation unit).
double& r1; // NOOOOO
extern double& r2; // OK, make sure it's defined elsewhere
"References behave like constant pointers who are automatically dereferenced"
auto i = ... ; // let the compiler deduce the appropriate type
decltype(expr) j = ... ; // declare the variable j as having the same type as expr
use using
instead of typedef
in C++
using txtptr = const char * (*) (double);
using y = x; // y is the new (hopefully more intuitive) name for the type x
Use nullptr
(of type nullptr_t
) instead of 0
or NULL
.
Favor the four kinds of modern C++ casts to standard C casts.
static_cast<new_type>(expr); // use for numeric types
const_cast<new_type>(expr); // get something non constant or non volatile
reinterpret_cast<new_type>(expr); // use for pointer cast, often nonsensical
dynamic_cast<new_type>(expr); // stoopid oop nonsense
A function whose result is declared to be constexr can be evaluated at compile time if its arguments are const, constexpr or litterals.

consteval
- specifies that a function is an immediate function, that is,
every call to the function must produce a compile-time constant.

Conditionally compile with if constexpr(expr)
where expr
is an expression
known at compile time.
static_assert(expr, "message"); // expr must be evaluated at compile time
double a[30];
for(double x : a)
x = 0; // STOOPID, x is a copy of an element of a
for(double& x : a)
x = 0; // OK, will modify, since x references an element of a
// if large elements, and no modification needed, favor a constant reference
for(const double& x : a) // or const auto&
std::cout << x;
double *p;
p = new double; // uninitialized
double *x = new double(5); // initialized to value 5
double *y = new double{6}; // initialized to value 6
double *z = new double(); // initialized to 0
double *p = new double[n]; // pointer to first element of
// array dynamically allocated
double *p = new double[3]{1.0, 3.0, 3.0}; // initialized version
delete p; // ok
double *q = reinterpret_cast<double*>(malloc(sizeof(int)));
delete q; // NOOOOO, STOOPID
Anything declared with new[]
must be deleted with delete[]
.
U.B. .
If a type T
aliases an array type, any allocated new T
must be unallocated
with delete[]
class TClass {
private:
// ... //
protected:
// ... //
public:
// ... //
}
// if absent, private
// can repeat sections
In C, struct X
declares a type struct X
, in C++ it declares a type X
(similar to class X
).
Always place class declaration in .h
header file with the same name as the
class, place the implementation in a .cpp
file with the same filename.
this
is a constant pointer to the object, it is automatically added to member
methods by compilers.
For a setter, returning *this
allows method chaining.
class T{
public:
RT method(params) const; // this method won't modify the object
};
If a method does not modify its calling object, declare it const
.
A const
method cannot call a non const
method. It can however call a static
method.
Functions who are friends of a class can access all fields and methods of this class.
Inline functions must be declared and defined (below) in header files. The compiler needs the definition.
A method defined in the class declaration is implicitly inline.
Inline functions can't be recursive.
The default constructor is invoked automatically if an object is not initialized.
If fields are initialized (using constants), the default constructor and all other constructors will not have to initialize the fields. (... helpfull)
If a constructor with parameters exists, the compiler won't provide the default parameterless constructor. To specifically request it, use:
class S{
public:
S() = default; // request the default parameterless constructor
};
class X{
public:
X(const X& x); // cloning constructor
};
X x;
X y(x); // same as
X y = x; // this
X y{x}; // same as
Always provide a cloning constructor for classes that have dynamically allocated
fields (pointers, new nonsense ... etc). A cloner for class T
has T(const T&)
for a signature.
class Z{
// assumed to have a parameterless AND a cloning constructor
// assumed to be assignable: Z z1, z2; ...; z1 = z2;
};
class X{
private:
int k_;
Z z_;
X(int k, const Z& z); // constructor
};
// NO, STOOPID
X::X(int k, const Z& z){
k_ = k;
z_ = z; // here z_ is initialized first with the parameterless constructor
// it is then reaffected using the cloning constructor
}
// CHAD
X::X(int k, const Z& z):k_(k), z_(z){ // call the cloning constructor first
}
class C{
public:
~C(); // Destructor
};
the delete
operator calls the destructor before freeing the memory dynamically
allocated with new
.
The expression
const_cast<T>(v)
can be used to change theconst
orvolatile
qualifiers of pointers or references. (Among new-style casts, onlyconst_cast<>
can remove const qualifiers.)T
must be a pointer, reference, or pointer-to-member type.
If an object can have multiple equivalent physical representations, consider
using the mutable
qualifier on fields that should be modifiable even if the
object is declared const
.
mutable
vs const_cast<...>(this)
: EPIC FIGHT
const_cast
: make all fields modifiable, just nowmutable
: permits modification of the class member declared mutable even if the containing object is declared const.
Operators that cannot be overloaded: ::
, .*
, .
, ?:
, sizeof
.
=
, []
, ()
, ->
: always overload these operators with methods.
T& operator=(const T&);
is the proper signature for the overloaded assignement
operator.
ostream& operator<<(ostream&, const T&)
is the (only) proper signature for
the overloaded <<
operator.
istream& operator<<(ostream&, T&)
is the (only) proper signature for
the overloaded >>
operator.
Call a method from within these two overloaded operators; this allows for subclasses to define their own standards for stream operations \(\rightarrow\) polymorphism.
x[j]
is equivalent to x.operator[](j)
explicit
specifies that a constructor is explicit, that is, it cannot be
used for implicit conversions and copy-initialization.
To convert an instance of class T
into an instance of type X
:
T t;
X x;
x = t; // is equivalent to
x = t.operator X(); // this
T.operator X() // -> convert the instance of type T into an equivalent instance of type X
class A
{
public:
int x;
protected:
int y;
private:
int z;
};
class B : public A
{
// x is public
// y is protected
// z is not accessible from B
};
class C : protected A
{
// x is protected
// y is protected
// z is not accessible from C
};
class D : private A // 'private' is default for classes
{
// x is private
// y is private
// z is not accessible from D
};
Constructors are never inherited.
Always call the parent constructor in the inherited constructor.
class Y{
private:
T1 t1_;
public:
Y(T1 t1);
};
Y::Y(T1 t1){
t1_ = t1;
}
class X : public Y{
private:
T2 t2_;
public:
X(T1 t1, T2 t2);
};
X::X(T1 t1, T2 t2) : Y(t1){
t2_ = t2;
}
// multiple inheritance
class Child: public parent1, private parent2 {};
A pointer (or reference) to class T
used to call method m()
uses the definition of m
in the class T
, even if the pointed instance is of
a subclass of T
which overrides m
.
#include <iostream>
using namespace std;
class X {
public:
void do_smthg();
virtual void do_smthg_else();
};
class Y : public X{
public:
void do_smthg();
void do_smthg_else() override;
};
void Y::do_smthg(){
cout << "y do\n";
}
void Y::do_smthg_else(){
cout << "y do\n";
}
void X::do_smthg(){
cout << "x do\n";
}
void X::do_smthg_else(){
cout << "x do\n";
}
int main(){
X *a[3];
a[0] = new Y();
a[0]->do_smthg(); // x do: static dispatch
a[0]->do_smthg_else(); // y do: dynamic dispatch
return 0;
}
use virtual
on base type, virtual
or preferrably override
on the child
type.
static
methods (class methods) and constructors cannot be virtual.
A call to a virtual method in the constructor of a base class uses the definition of the method in the base class. Avoid calling virtual methods in a constructor.
If polymorphism is used, USE A VIRTUAL DESTRUCTOR. Why ? Because.
If you have a virtual function that is not abstract, then you must implement it.
To declare a method abstract:
virtual qualifiers returntype method(params) qualifiers = 0;
// also called pure virtual method
A class having at least one abstract method is considered abstract, it can't be instanciated (incomplete class).
An interface is a class that only contains pure virtual methods.
A destructor can be declared virtual (10.3) or pure virtual (10.4); if any objects of that class or any derived class are created in the program, the destructor shall be defined. If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly-declared) is virtual.
The destructor of a base class is always called from the destructor of the derived class, even if it is declared virtual pure. It must always be implemented.
An abstract subclass of an abstract class can implement part of a virtual pure method, and still declare it virtual pure; its subclasses can then call its implementation in their (hopefully) final implementation. Still, DECLARE IT VIRTUAL PURE.
If a method is supposed to override a virtual method in a base class, use the
override
keyword, this way if the method signatures don't match (forgot
const
for example), there will be a compile time error.
To overload a method in a base class, reinject it into the scope
using Base::method;
.
See unqualified name lookup in
StackOverflow
and cppreference
.
Templates
template <int k, typename R, typename T>
T f(R){
for(int n=0; n<k; k++)
// ...
}
either a type T
or int
param or bool
param. can also be used for a class.
template default values
template <int d, typename NT> // ok
class X{
// ...
};
template <int d, typename NT=double> // ok
class X{
// ...
};
template <int d=3, typename NT=double> // ok
class X{
// ...
};
X<> ; // ok
X<2> ; // ok
X<2,double> ; // ok
X<,double> ; // no
template <int d=3, typename NT> // NOOOO
class X{
// ...
};
Stack allocated objects created within a try bloc before an exception is thrown are automatically deleted. (NOT tru for dynamically allocated objects with new).
try{
int k;
std::cin >> k;
if(k<0)
throw k;
else if(k==0)
throw std::exception;
}catch(int i){
// if exception is of type int, execute this and jump after all catch blocks
}catch(std::exception e){
}
Standard Exceptions are in the header <stdexcept>
,
they all inherit from std::exception
.
Favor using exception classes instead of string and numeric constants in catch block parameters. (create classes that inherit from the std::exception class).
- An exception forces calling code to recognize an error condition and handle it. Unhandled exceptions stop program execution.
- An exception jumps to the point in the call stack that can handle the error. Intermediate functions can let the exception propagate. They don't have to coordinate with other layers.
- The exception stack-unwinding mechanism destroys all objects in scope after an exception is thrown, according to well-defined rules.
- An exception enables a clean separation between the code that detects the error and the code that handles the error.
try{
executeProgram();
}catch(...){ // universal exception handler, catch all exceptions, unknown
prepareProgramStop();
}
// some dynamic memory is allocated
// some exception is thrown
catch(...){
// deallocate memory
throw; // rethrow the exception
}
class X{
public:
X(const X&) = delete; // don't allow copy constructor
};
See StackOverflow
for more details on method = delete
.
See Resource Acquisition is initialization (RAII) on wikipedia.
// careful, slicing: will only get base type and message
catch(std::exception e)
catch(const std::exception e)
// ok, virtual message, no slicing
catch(const std::exception& e) // favor using const reference -> no slicing
The catch
exception handlers are examined in order when an exception is
thrown in the try
block; as soon as one has a matching parameter type (even
if superclass), its block is executed. The other catch
blocks are all skipped
, even when the parameters match. ORDEEEER!
Throwing an exception should never cause a memory leak, use RAII.
IOS INBREEDING





std::cout
, std::cerr
and std::clog
are all instances of std::ostream
,
ie: std::basic_ostream<char>
.
std::cin
is an instance of std::istream
,
ie: std::basic_istream<char>
.
A stream can have four (non exclusive) states: fail
, good
, bad
and eof
.
- fail: error, but no data loss, cant read or write from now on
- bad: error, with data loss, cant read or write from now on
- eof: end of stream encountered, not an error, but cant read or write from now on
- good: good ... (duh)
a stream is automatically cast to a bool using !fail()
. It is considered
true
if the last IO operation succeded.
while(std::cin){ // while the last input was successful <==> !(std::cin.fail())
// continue processing
}
Force stream state
std::cin.clear(std::ios_base::failbit);
std::cin.clear(std::ios_base::badbit);
std::cin.clear(std::ios_base::eofbit);
std::cin.clear(); // defaults to std::ios_base::goodbit
A stream's openmode is of type std::ios_base::openmode
. It is a bitmask type
ie: can be combined with |
;
ex: (ios_base::in | ios_base::out | ios_base::binary )
See cppreference
for
more information.
Input Stream Methods
-
std::ios_base::app
comes from 'append' - all output will be added (appended) to the end of the file. In other words you cannot write anywhere else in the file but at the end. -
std::ios_base::ate
comes from 'at end' - it sets the stream position at the end of the file when you open it, but you are free to move it around (seek) and write wherever it pleases you.
More details on openmodes.
is::get(char &)
-> get a char from a stream. returns the stream. Does not ignore non printable chars.is::unget()
-> last char read is put back in the buffer, as if it was never read also returns the stream.is::putback(char_type)
-> char parameter is put in the buffer, also returns the stream.is::peek()
-> returns the next character in the stream, does not read it.getline(input_stream, string_storage, delimiter)
-> reads until delimiter or EOF and writes into string until length is reached. seecppreference
.is::read(char* storage, count)
-> reads and stores into storage until count or EOF is reached. Seecppreference
.basic_istream& ignore(std::streamsize count=1, int_type delim=Traits::eof());
bruh look it up.
Some input streams (non interactive ones, unline std::cin) support positioning
with the tellg()
and
seekg(pos | offset, seekdir)
functions.
seekdir
can be ios_base::beg
, ios_base::end
or ios_base::cur
.
Positioning often requires a stream to be open in std::io_base::binary
.
std::ifstream
follows RAII idiom, its constructor takes a file name as a string
and an optional (default = std::ios_base::in
) std::ios_base::openmode
;
see more on cppreference
.
The rdbuf()
method returns a pointer to the stream_buffer associated with the
basic_ios.
cppreference
.
#include <fstream>
#include <iostream>
int main(){
std::ifstream dat("test.dat", std::ios_base::in | std::ios_base::binary);
std::cout << "ifstream rdbuffer is open? " << dat.rdbuf()->is_open() << std::endl;
dat.rdbuf()->close();
std::cout << "ifstream rdbuffer is open? " << dat.rdbuf()->is_open() << std::endl;
return 0;
}
The void open( const char* filename,std::ios_base::openmode mode=std::ios_base::in)
method opens and associates the file with name filename
with the file stream.
See cppreference
for more
details.
The void close()
method of ifstream
closes the associated file.
This function is called by the destructor of basic_fstream when the stream
object goes out of scope and is not usually invoked directly.
cppreference
.
The method bool is_open()
checks if the file stream has an associated file.
cppreference
.
Used to ckeck if a file opened for reading exists.
The global getline() function works with C++ std::string objects. The istream::getline() methods work with "classic" C strings (pointers to char).
Use istringstream
to convert a string into an objet whose class supports the >>
operator.
istringstream::str()
returns a copy of the underlying string.
#include <iostream>
#include <sstream>
int main(){
std::string s1 = "1 2";
std::istringstream is(s1);
// obtain string in the input string stream
std::cout << is.str() << std::endl; // prints: 1 2
// change string in the input string stream
std::string s2 = "3 4";
is.str(s2);
int a, b;
is >> a >> b; // read input string stream
std::cout << "a=" << a << " b=" << b << std::endl; // prints: a=3 b=4
return 0;
}
istringstream
supports
positioning with seekg(...)
and tellg()
.
Similarly to istream
,
ostream
can handle 3 kinds of operations:
- formatted (output) with
<<
- unformatted (output) with
put
andwrite(char*, count)
- positioning with
tellp()
andwrite(pos | offset, seekdir)
ofstream
has a destructor that automagically closes the associated file \(\rightarrow\)
RAII. A useful constructor is
ofstream(filename, openmode)
.
ofstream
also has the usual
rdbuf
,
open
,
close
and
is_open
methods.
Use ostringstream
to convert an objet whose class supports the <<
operator into a string.
Its constructor takes a char sequence and an openmode
(default is ios_base::out
). Its
str
method
manages the contents of the underlying string object, that object can be changed
through it.
The str
method
comes is overloaded:
- one version (<=3) returns a copy of the string associated with the ostringstream.
- another version (>=4) can copy a string into the internal
stringbuf
.
#include <iostream>
#include <cmath>
#include <sstream>
template <typename T>
std::string toString(const T& x){
std::ostringstream os(std::ios_base::app); // append to file
std::string buf = "*"; // arbitrary prefix to show the effect of append mode
os.str(buf);
os << x;
return os.str();
}
int main(){
std::string pi_str = toString(std::acos(-1.0));
std::cout << "PI=" << pi_str << std::endl; // PI=*3.14159
return 0;
}
Some important io manipulators in <ios>
#include <iomanip>
#include <ios>
#include <iostream>
#include <sstream>
int main() {
bool v = true;
// boolalpha , noboolalpha
std::cout << std::boolalpha << v << " " << std::noboolalpha << v << std::endl;
v = false; // print: true 1
std::cout << std::boolalpha << v << " " << std::noboolalpha << v
<< std::endl; // print: false 0
// showbase, noshowbase, dec, hex, oct
std::cout << std::showbase;
std::cout << std::dec << 15 << " " << std::hex << 15 << std::oct << " " << 15
<< std::endl; // prints: 15 0xf 017
std::cout << std::noshowbase;
std::cout << std::dec << 15 << " " << std::hex << 15 << std::oct << " " << 15
<< std::endl; // prints: 15 f 17
// showpoint, noshowpoint
double d = 33.0, q = 33.1;
std::cout << std::showpoint << d << " " << std::noshowpoint << d << " " << q
<< std::endl; // prints: 33.0000 33 33.1
// showpos, noshowpos
double pi = 3.14159;
std::cout << std::showpos << pi << " " << 0.0 << " " << std::noshowpos << pi
<< std::endl; // prints: +3.14159 +0 3.14159
// skipws, noskipws
char c1, c2, c3;
std::cout << "type>";
std::cin >> std::skipws >> c1 >> std::noskipws >> c2 >>
c3; // type: <space> A <space> B
std::cout << std::hex << std::showbase << static_cast<int>(c1) << " "
<< static_cast<int>(c2) << " " << static_cast<int>(c3)
<< std::endl; // prints: 0x41 0x20 0x42
// scientific, fixed, defaultfloat, hexfloat
double x = 0.00002;
std::cout << std::fixed << x << std::endl; // prints: 0.000020
std::cout << std::scientific << x << std::endl; // prints: 2.000000e-05
std::cout << std::defaultfloat << x << std::endl; // prints: 2e-05
std::cout << std::hexfloat << x << std::endl; // prints: 0x1.4f8b588e368f1p-16
// uppercase, nouppercase
double y = 300.1234;
int k = -559038737;
std::cout << std::scientific << std::hex;
std::cout << std::uppercase << y << " " << k
<< std::endl; // prints: 3.001234E+02 0XDEADBEEF
std::cout << std::nouppercase << y << " " << k
<< std::endl; // prints: 3.001234e+02 0xdeadbeef
// std::setfill, std::left, std::right and std::internal
std::cout << std::hex << std::showbase;
std::cout << std::setfill('#') << std::setw(12);
std::cout << std::left << 24 << std::endl; // prints: 0x18########
std::cout << std::setfill('#') << std::setw(12);
std::cout << std::right << 24 << std::endl; // prints: ########0x18
std::cout << std::setfill('#') << std::setw(12);
std::cout << std::internal << 24 << std::endl; // prints: 0x########18
// std::ws
char c4, c5;
std::cout << "type>";
std::cin >> std::skipws >> c4 >> std::noskipws >> std::ws >>
c5; // type: <space> A <space> B
std::cout << std::hex << std::showbase << static_cast<int>(c4) << " "
<< static_cast<int>(c5) << std::endl; // prints: 0x41 0x42
// std::ends
std::ostringstream os;
os << "AAA" << std::ends << "BBB" << std::ends << "CCC" << std::ends
<< std::flush; // make c style strings
std::string s = os.str();
for (auto c : s)
std::cout << std::hex << std::showbase << static_cast<int>(c) << " ";
// prints: 0x41 0x41 0x41 0 0x42 0x42 0x42 0 0x43 0x43 0x43 0
std::cout << std::endl;
return 0;
}
unitbuf/nounitbuf
enables or disables automatic flushing of the output stream after any
output operation.
The oracle at stackoverflow
informs us
that cerr/wcerr
default to
unitbuf
. You do want to see the errors if a crash happens.
What's the difference between ws
and skipws
?
🔮
-
skipws
enables or disables skipping of leading whitespace by the formatted input functions. -
ws
discards leading whitespace from an input stream \(\rightarrow\)unformatted input function
<ostream>
declares 3 manipulators:
flush
flushes the stream ie: writes all unwritten data on the stream buffer to the peripheral (file, string, whatever else),unitbuf
performs a flush after each operation.endl
inserts a newline character into the output stream and flushes it.ends
inserts a null character into the output stream, does not flush.
setw(n)
(declared in <iomanip>
) specifies that the next operation should use a width n
.
If write: shorter than n
will be padded with the filler char set by
setfill
, longer won't be troncated.- If read: shorter than
n
is read entirely, longer is troncated (unless numeric).
setbase
sets the numeric base of the stream.
setfill
is self
explanatory ... bruh.
setprecision
set the precision used to write or read floating point types, see the page
reference examples.
Formatted io for basic types is symmetic: a value written with <<
can
be read with >>
(without loss of information). The only exception is the string
class: the <<
operator writes the whole string, but the >>
operator only
reads a word into the string.
The quoted
allows insertion and extraction of quoted strings,
such as the ones found in csv, json or xml.
#include <iomanip>
#include <iostream>
int main(){
std::string s, q = "c++ is fabulous";
std::stringstream flow;
flow << q;
flow >> s;
std::cout << s << std::endl; // prints: c++
std::stringstream qflow; // implicit "
qflow << std::quoted(q);
qflow >> std::quoted(s);
std::cout << s << std::endl; // prints: c++ is fabulous
std::stringstream bflow;
bflow << std::quoted(q, '|');
bflow >> std::quoted(s, '|');
std::cout << s << std::endl; // prints: c++ is fabulous
std::stringstream xflow;
xflow << std::quoted(q, '<', '>');
xflow >> std::quoted(s, '<', '>');
std::cout << s << std::endl; // prints: c++ is fabulous
return 0;
}
To create a paramterless output manipulator use the 18-20 overload
of the <<
operator of the basic_ostream
class.
// prototype
std::basic_ostream& operator<<(
std::basic_ostream<CharT, Traits>& (*func)
(std::basic_ostream<CharT, Traits>&) );
// so if a function manip is declared as
std::ostream& manip(std::ostream&);
// the expression
flow << manip; // is equivalent to
manip(flow);
Example:
#include <iostream>
#include <ostream>
template<typename C, typename T>
std::basic_ostream<C,T>& compilation_date(std::basic_ostream<C,T>& os){
return os << __DATE__ << " " << __TIME__ << std::flush;
}
int main(){
std::cout << "Version : " << compilation_date;
std::endl(std::cout); // equivalent to
std::cout << std::endl;
return 0;
}
Left and right shift nonsense
std::cout << (4<<2) << std::endl; // equivalent to 4 * 2^2
std::cout << (8>>2) << std::endl; // equivalent to 8 / 2^2
Lambda expressions in C++: C++11 onwards
- the return type is automatically is automagically deduced by the compiler.
auto f = [](const char *s=" default\n"){std::cout << 42 << s << std::endl;};
f(); // prints: 42 default
f(" not default"); // prints: 42 not default
[](int n){std::cout << n << std::endl;}(32); // prints: 32
Use decltype(name)
to get the type of a lambda function named name
(often
declared auto). Two lambda expressions, even if having the exact same definitions
don't have the same type. In a generic context, we need to name a lambda expression
to use its type as a template parameter.
Starting from C++17, the compiler can infer the template parameters of an instance from the types of the constructor real parameters. So can do dis:
auto lam = [](int n){return 2*n};
Feval<int, decltype(lam)> f(2, lam);
Feval f(2, lam); // works and is equivalent to
Feval f(2, [](int n){return 2*n;}); // dis
Starting from C++14, lambda parameters can be auto: works as if there was
a generic type for each auto
parameter.
auto maxi = [](auto a, auto b){
if(a>b) return a; else return b;
};
std::cout << maxi(1, 3) << std::endl; // prints: 3
std::cout << maxi(1, 0) << std::endl; // prints: 1
std::cout << maxi('a', 'c') << std::endl; // prints: c
std::cout << maxi(1, 3.14) << std::endl; // does not compile
// maxi would have to return either a or b of type int or double -- incompatible
// " Inconsistent types 'double' and 'int'
to force a lambda to return a type:
auto maxi = [](auto a, auto b) -> decltype(a) {
if(a>b) return a; else return b;
};
std::cout << maxi(1, 3.14) << std::endl; // now compiles and prints: 3
Other example
auto div3 = [](int n=3) -> bool {
return n%3==0;
};
std::cout << div3() << std::endl; // prints: 1
std::cout << div3(43) << std::endl; // prints: 0
A lambda can access all global and static
local variables in the scope in
which it is defined. To capture a (nonstatic!) variable in the local context,
use the []
operator to specify what local entities to capture:
#include <iostream>
int main() {
int x = 1, y = 2, w = 3;
auto impure = [=]() {
std::cout << x << std::endl;
}; // total capture, can use x, y, w and any variable defined in main are
// accessible to impure
impure(); // prints: 1
auto heathen = [w, y](int k = 0) {
std::cout << w + k << y + k << std::endl;
}; // selective capture, can only use w, y and global or static local
// variables.
heathen(2); // prints: 54
// auto stoopid = [x, w]() {
// return x + y;
// }; // has no access to y, so skoozy no compilo, no habla ingles
x = 3;
impure(); // prints: 1
// x was captured with value 1, at the definition of impure, not at this call
auto smort = [k = x, n = y]() {
std::cout << k << n << std::endl;
}; // rename param
smort(); // prints: 3
auto modx = [&]() { x = 7; }; // total reference capture, can modify all
// variables in the calling context.
std::cout << x << std::endl;
modx(); // modifies x
std::cout << x << std::endl;
int nonsense1 = 2, nonsense2 = 1;
auto affine = [&a = nonsense1, &b = nonsense2](int x) {
return a * x + b;
}; // selective reference capture with renaming
std::cout << affine(0) << std::endl; // prints: 1
std::cout << affine(3) << std::endl; // prints: 7
return 0;
}
Reference captures can cause UB.
double *nonsense = new double(3);
auto mulNonsense = [&a = *nonsense](double x) { return a * x; };
std::cout << mulNonsense(4.0) << std::endl; // prints: 12.0
delete nonsense;
std::cout << mulNonsense(4.0)
<< std::endl; // UB, dangling reference used in lambda
Avoid three things with lambdas:
- functions that return lambdas that capture by reference any elements of the function context.
- a lambda capturing references of the current context is stored for futher use
(using the generic
function
wrapper for example). - thread shenanigans with lambdas capturing by reference.
// an example of a mixed capture
#include <iostream>
int main() {
int x = 32, y = 3;
std::cout << "x=" << x << " y=" << y << std::endl; // prints: x=32 y=3
[&a = x, b = y]() { a = b;}();
std::cout << "x=" << x << " y=" << y << std::endl; // prints: x=3 y=3
return 0;
}
An example of relevant uses of lambdas:
#include <iostream>
// the common reduce function common in the functional paradigm
// T is a type, BinF is a callable object with the parameters (T,T) which return a T
template <typename T, typename F>
// init is the initial value, list contains the other values in the calculation
T reduce(T list[], int n, T init, F f) {
for (int i = 0; i < n; i++){
init = f(init, list[i]);
}
return init;
}
int main() {
int v[] = {1, 2, 3, 4, 5, 6, 7, 8};
int nv = sizeof(v) / sizeof(*v); // number of values in v
int r1 = reduce(v, nv, 0, [](int a, int b) { return a + b; });
// performs summations, prints: 36
int r2 = reduce(v, nv, 1, [](int a, int b) { return a * b; });
// performs products, prints: 40320
int r3 = reduce(v, nv, 0, [](int a, int b) { return a ^ b; });
// performs consecutive xor's (checksum), prints: 8
std::cout << r1 << " " << r2 << " " << r3 << std::endl;
return 0;
}
Let M
> N
and f
be a function of M
parameters. Currying is the
process of creating a function g
of N
parameters by fixing M-N
parameters
in f
.
example:
#include <iostream>
int main() {
// lambda returning lambda
auto in = [](auto min, decltype(min) max) {
return [min, max](decltype(min) valeur) -> bool {
return valeur >= min && valeur <= max;
};
};
auto lowercase = in('a', 'z');
std::cout << std::boolalpha << lowercase('v') << std::endl; // prints: true
std::cout << lowercase('V') << std::endl; // prints: false
auto proba = in(0.0, 1); // is a valid probability value
std::cout << proba(2) << std::endl; // prints: false
std::cout << proba(0) << std::endl; // prints: true
std::cout << proba(1) << std::endl; // prints: true
std::cout << proba(0.4) << std::endl; // prints: true
return 0;
}
The Standard Template Library contains
- Algorithms
- Containers (~15 class templates)
- Iterators
std::list
implements a
bidirectional linked list.
An iterator is a pointer-like object used to cycle through all
the elements stored in a container.
Iterators are a standard way to access a container. Each container type X
provides a type X::iterator
to access its elements. The begin()
method
returns an iterator that points to the first element of the container. Its value
can be accessed by using the *
operator as if it was a pointer dereference.
The ++
operator applied (suffix or prefix) to an iterator goes to the next
element in the container.
ITERATORS ARE NOT POINTERS.
All containers offer at least the begin()
and end()
methods.
Careful, The end()
method returns an iterator to the element
following the last element, not the last element.
NEVER DEREFERENCE THE OPERATOR RETURNED BY end()
.
However, one can compare that iterator with another iterator of the same type and pointing to an element of the same container.
We can iterate through a container by using an iterator that goes through \([c.begin(), c.end()[\)
Application
#include <iostream>
#include <list>
#include <vector>
template <typename C> void displayAll(C &c) {
// C::iterator it; // does'nt work, compiler does'nt know that C::iterator is
// a type and not a member of class C
typename C::iterator it; // specify explicitly that C::iterator is a type, and
// declare it to be of that type
for (it = c.begin(); it != c.end(); it++)
std::cout << *it << " ";
// // or in C++11 onward:
// for(auto it = c.begin(); it!=c.end(); it++)
// std::cout << *it << " ";
std::cout << std::endl;
}
int main() {
std::list<double> li = {1, 2, 3, 4};
std::vector<double> ve = {20, 30, 70};
displayAll(li); // prints: 1 2 3 4
displayAll(ve); // prints: 20 30 70
return 0;
}
There is not necessarily an order relation between iterators, USE !=c.end()
and
not <c.end()
.
If there is no need to modify the container elements, use a
const_iterator
template <typename C> void displayAll(const C &c) {
for(auto it = c.begin(); it!=c.end(); it++) // it is a const_iterator
std::cout << *it << " ";
std::cout << std::endl;
}
Iterator form | Description |
---|---|
input iterator | Read only, forward moving |
output iterator | Write only, forward moving |
forward iterator | Both read and write, forward moving |
bidirectional iterator | Read and write, forward and backward moving |
random access iterator | Read and write, Red and write, random access |

There are three principal types of iterators:
- Iterators that move forward (
input_iterator
,output_iterator
andforward_iterator
) and the associated named requirements. operators:++
,*
,==
,!=
. More details on theforward_iterator
can be found onQuora
,Oracle Docs
andCPlusPlus.com
. - Bidirectional Iterators like
bidirectional_iterator
also implement the--
operator. - Random Access Iterators implement the
[]
,+
,+=
,-
,-=
,<
,<=
,>
,>=
on top of the other usual operators. There exists an order relation among Random Access Iterators of a container. An example is the thestd::vector::iterator
class andstd::random_access_iterator
.
Output iterators are never constant, and input iterators always are.
Ordinary pointers are random access iterators, which are a superset of output iterators.
See CPlusPlus.com
,
Oracle Docs
and
CPlusPlus.com
for a general
view of iterators. (especially the Oracle Docs, good stuff).
std::vector
is a dynamic
size array.
Vector<T, A>
T is the type of the elements stored, A is the type of the allocators
that can manage the memory of the elements.
Useful constuctors of the vector
class:
// empty vector
std::vector<int> vi;
// vector initialized with 7 elements equal to 3
std::vector<int> vd(7, 3);
// vector initialized with initializer list
std::vector<int> vil({1,2,3,4});
// vector initialized with initializer list
std::vector<int> wil = {1,2,3,4};
// vector initialized with the copy constructor
std::vector<int> cwil=wil;
// vector initialized with the copy constructor (same)
std::vector<int> c2wil(wil);
#include <iostream>
#include <iterator>
#include <vector>
class X {
double x_;
char c_;
public:
X(double x, char c = 'c') : x_(x), c_(c) {}
friend std::ostream &operator<<(std::ostream &os, const X &x) {
return os << x.c_ << ":" << x.x_;
}
};
template <typename T>
std::ostream &operator<<(std::ostream &os, std::vector<T> v) {
os << "{";
for (auto val = v.begin(); val != v.end(); val++) {
os << *val << ",";
}
return os << "}";
}
int main() {
std::vector<X> v;
// construct at the end of the vector
v.emplace_back(2, '3');
std::cout << v << std::endl; // prints: {3:2,}
// insert at the end of the vector
v.push_back(X(2));
std::cout << v << std::endl; // prints: {3:2, c:2}
// insert using a back insert iterator,
std::back_insert_iterator<std::vector<X>> i(v);
// litteraly just calls push_back according to the doc
*i++ = X(3.14);
*i++ = X(1. / 2);
std::cout << v << std::endl; // prints: {3:2,c:2,c:3.14,c:0.5,}
// insert using an insert iterator, start inserting at start+2
std::insert_iterator<std::vector<X>> ia(v, v.begin()+2);
// litteraly just calls insert at 2 according to the doc
*ia++ = X(13);
std::cout << v << std::endl; // prints: {3:2,c:2,c:13,c:3.14,c:0.5,}
return 0;
}
emplace_back
passes its arguments to the constructor of the class, it creates a new object and
appends it. This saves the cost of copying an intermediary object.
See more on the
back_insert_iterator
and
insert_iterator
.
Vector elements can be accessed:
- with the
at
method; accessess the elements at the specified index, it returns a reference to it. throws anstd::out_of_range
exception if incompatible index. - the
[]
operator; UB if index incompatible index. - by dereferencing an iterator pointing to some vector element. UB if invalid.
- using a range based for loop (C++>=11)
- using the
front
andback
methods which return references to the first and last element in the vector.
std::vector<int> v(3,5);
for(const auto& val : v) // uses iterators in the background
std::cout << val << std::endl;
Vector elements are necessarily contiguous in memory (dictated by the C++ standard).
std::vector<int> v({1,2,3,4,5});
int *p = v.data();
std::cout << *p << std::endl; // prints: 1
p = &v[3];
std::cout << *p << std::endl; // prints: 4
std::cout << *(&v[0] + 3) << std::endl; // prints: 4
Use the
capacity()
method
to get the storage size of that array (always >= #elems).
Use the
resize(newsize, optional filler)
method to resize the vector container and fill the new elements with a filler
default value.
Application: using a pointer to traverse a vector
std::vector<int> v({1,2,3,4,5});
auto p = &v[0];
auto pmax = p+v.size();
for(; p!=pmax; p++)
std::cout << *p << " "; // prints: 1 2 3 4 5
operation | cost |
---|---|
memory | \(\propto N\) |
insertion at the front | \(\propto N\) |
insertion at the back | Constant (and cheap) |
insertion at position \(p\) | \(\propto (N-p)\) |
deletion at the front | \(\propto N\) |
deletion at the back | Constant (and cheap) |
deletion at position \(p\) | \(\propto (N-p)\) |
access via index | Constant (and very cheap) |
reference and pointer invalidation on modification | YES ⚠️ |
iterator invalidation on modification | YES ⚠️ |
std::deque
is double-ended
queue. Insertions and deletions at the front and back are constant time.
The elements of a deque are not contiguous in memory \( \rightarrow \) can't iterate with a pointer.
Some useful constructors for a deque
// empty deque
std::deque<int> di;
// deque containing 10 elements all with value 5
std::deque<int> dd(10, 5);
// deque initialized with an initializer_list
std::deque<int> dil1({1, 2, 3, 4, 5});
// deque initialized with an initializer_list (same)
std::deque<int> dil2 = {1, 2, 3, 4, 5};
// cloning constructor
std::deque<int> dc1(di);
// cloning constructor (same)
std::deque<int> dc2=di;
deque
provides the
push_back
,
emplace_back
,
push_front
and the
emplace_front
methods.
we can also use
back_insert_iterator
,
front_insert_iterator
,
insert_iterator
,
for example:
// assume di is an empty deque<int>
std::back_insert_iterator<std::deque<int>> i(di);
*i++ = 100;
*i++ = 299;
for (const auto &val : di)
std::cout << val << " "; // prints: 100 299
std::cout << std::endl;
std::front_insert_iterator<std::deque<int>> fi(di);
*fi++ = 0;
*fi++ = 1;
for (const auto &val : di)
std::cout << val << " "; // prints: 1 0 100 299
std::cout << std::endl;
std::insert_iterator<std::deque<int>> ii(di, di.begin()+2);
*ii++ = -1;
*ii++ = -2;
for (const auto &val : di)
std::cout << val << " "; // prints: 1 0 -1 -2 100 299
std::cout << std::endl;
Deque elements can be accessed:
- using the
at
method; accessess the elements at the specified index, it returns a reference to it. throws anstd::out_of_range
exception if incompatible index. - using the
[]
operator; UB if index incompatible index. - by dereferencing an iterator pointing to some vector element. UB if invalid.
- using a range based for loop (C++>=11) // same as with vector, no example here
- using the
front
andback
methods which return references to the first and last element in the deque.
operation | cost |
---|---|
memory | \(\propto N\) (> vector) |
insertion at the front | Constant (and cheap) |
insertion at the back | Constant (and cheap) |
insertion at position \(p\) | \(\propto N\) |
deletion at the front | Constant (and cheap) |
deletion at the back | Constant (and cheap) |
deletion at position \(p\) | \(\propto N\) |
access via index | Constant (>vector) |
reference and pointer invalidation on modification | NO ✅️ |
iterator invalidation on modification | YES ⚠️ |
Use a deque when operations at the front are common and the number of elements is high.
std::deque
(double-ended queue) is an indexed sequence container that allows fast insertion and deletion at both its beginning and its end. In addition, insertion and deletion at either end of a deque never invalidates pointers or references to the rest of the elements.
As opposed to std::vector, the elements of a deque are not stored contiguously: typical implementations use a sequence of individually allocated fixed-size arrays, with additional bookkeeping, which means indexed access to deque must perform two pointer dereferences, compared to vector's indexed access which performs only one.
The storage of a deque is automatically expanded and contracted as needed. Expansion of a deque is cheaper than the expansion of a std::vector because it does not involve copying of the existing elements to a new memory location. On the other hand, deques typically have large minimal memory cost; a deque holding just one element has to allocate its full internal array (e.g. 8 times the object size on 64-bit libstdc++; 16 times the object size or 4096 bytes, whichever is larger, on 64-bit libc++).
The complexity (efficiency) of common operations on deques is as follows: Random access - constant O(1). Insertion or removal of elements at the end or beginning - constant O(1). Insertion or removal of elements - linear O(n).
How deque is implemented:
stackoverflow
An std::array<T, N>
cannot be extended.
Useful constructors:
std::array<int,5> a0;
for(const auto& x:a0) std::cout << x << " ";
// prints: whatever was stored there before, garbage
std::cout << std::endl;
std::array<int,5> a1 = {1,2,3,4};
// std::array<int,5> a1({1,2,3,4}); // equivalent
for(const auto& x:a1) std::cout << x << " ";
// prints: 1 2 3 4 0
std::cout << std::endl;
// std::array<int, 5> a2 = {1,2,3,4,5,6}; // compilation error
std::array<int, 5> c(a1); // cloning constructor
for(const auto& x:c) std::cout << x << " ";
// prints: 1 2 3 4 0
std::cout << std::endl;
The logical size of an array
is always equal to its physical size.
Can't insert elements, can overwrite.
Array elements can be accessed:
- using the
at
method; accessess the elements at the specified index, it returns a reference to it. throws anstd::out_of_range
exception if incompatible index. - using the
[]
operator; UB if index incompatible index. - by dereferencing an iterator pointing to some array element, UB if invalid.
- using a range based for loop (C++>=11)
- using the
front
andback
methods which return references to the first and last element in the array. - using the
data
method, which: returns a pointer to the underlying array serving as element storage. The pointer is such that range[data(), data() + size()]
is always a valid range, even if the container is empty (data()
is not dereferenceable in that case).
Contrary to a C style array, an std::array
object is not automatically converted
to a pointer to its first element; for a function to receive an std::array<T, N>
use a formal parameter of type std::array<T, N>
or std::array<T,N>&
but never
T*
.
operation | cost |
---|---|
memory | \(\propto N\) |
insertions and deletions | NO ES POSIBLE! |
access via index | Constant |
reference and pointer invalidation on modification | NO ✅️ |
iterator invalidation on modification | NO ✅️ |
The std::array<T,N>
container is NEVER REALLOCATED.
Use an array
if you want to use C style arrays with an OO interface.
std::array::size()
is constexpr
. Two arrays of different sizes don't even
have the same type (bcoz template param N
is the size).
C
is the character typeT
is the traits type (used to manipulate objects of typeC
)A
is the allocater type for typeC
classes defined in <string>
:
name | definition | encoding |
---|---|---|
string | basic_string | ASCII |
wstring | basic_string<wchar_t> | unicode 16 or 32 |
u16string | basic_string<char16_t> | unicode 16 |
u32string | basic_string<char32_t> | unicode 32 |
characters in the string class are stored contiguously.
some useful constructors of std::string
std::string s1;
std::cout << "[" << s1 << "]" << std::endl; // []
std::string s2 = "abcdf fjd";
std::cout << "[" << s2 << "]" << std::endl; // [abcdf fjd]
std::string s3 = s2;
std::cout << "[" << s3 << "]" << std::endl; // [abcdf fjd]
std::string s4(s2, 4, 3);
std::cout << "[" << s4 << "]" << std::endl; // [f f]
std::string s5 = {'a', 'e', 'i', 'o', 'u', 'y'};
std::cout << "[" << s5 << "]" << std::endl; // [aeiouy]
std::string s6(6, '-');
std::cout << "[" << s6 << "]" << std::endl; // [------]
String characters can be accessed:
- using the
at
method; accessess the elements at the specified index, it returns a reference to it. throws anstd::out_of_range
exception if incompatible index. - using the
[]
operator; UB if index incompatible index. - by dereferencing an iterator pointing to some character in the string, UB if invalid.
- using a range based for loop (C++>=11)
- using the
front
andback
methods which return references to the first and last characters in the string. - using the
c_str
method, which: returns a pointer to a null-terminated character array with data equivalent to those stored in the string (ie: an equivalent C style string).
operation | cost |
---|---|
memory | \(\propto N\) |
insertions and deletions at the end | Constant |
insertions and deletions at the beginning | \(\propto N\) |
insertions and deletions at the position \(p\) | \(\propto (N-p)\) |
access via index | Constant |
reference and pointer invalidation on modification | YES ⚠️ |
iterator invalidation on modification | YES ⚠️ |
void f(const string&);
accepts the parameter "c style string"
, convesion
by constructor: f (string("c style string"))
. To get a string
instance
directly, use the suffix s
after the "
. f("real string instance"s)
,
this way no data needs to be copied.
list<T,A>
and
forward_list<T,A>
are declared in the <list>
and <forward_list>
headers respectively.
They are both parametrised by:
T
the type of elements stored.A
the type of allocators used for 'T' (default isstd::allocator<T>
).
typical implementation of a list (doubly linked list):

typical implementation of a forward_list (linked list):

The implementations are non intrusive: the stored elements don't need to store pointers to ther successors and predecessors.
#include <iostream>
#include <list>
#include <string>
template <typename T> void printList(const std::list<T> &l) {
std::cout << '[';
for (const T &e : l)
std::cout << e << " ";
std::cout << ']' << std::endl;
}
int main() {
std::list<int> li1;
printList(li1); // prints: []
std::list<std::string> li2 = {"first", "second"};
printList(li2); // prints: [first second ]
std::list<std::string> li3 = li2;
printList(li3); // prints: [first second ]
auto istart_li2 = li2.begin();
auto iend_li2 = li2.end();
// can't just do li2.begin()+1 , list has a bidirectional operator, which
// is not a random access iterator
istart_li2++; // ignore first element of li2
std::list<std::string> li4(istart_li2, iend_li2);
printList(li4); // prints: [second ]
std::list<double> li5(4, 3.14159);
printList(li5); // prints: [3.14159 3.14159 3.14159 3.14159 ]
return 0;
}
List elements can be accessed by
- dereferencing an iterator pointing to some list element. UB if invalid.
- using a range based for loop (C++>=11).
- using the
list::front
andlist::back
methods which return references to the first and last element in the list. (analogous methods are provided forforward_list
... RTFD)
NO RANDOM ACCESS
To delete all elements in a list, use the
list::clear
and
forward_list::clear
methods.
Insert elements before an iterator's position using the
insert
method.
For a foward_list
, it is inefficient to insert before a specified item, thus
the insert_after
method is provided.
The
emplace
and
emplace_after
methods play analogous roles, the elements are constructed in-place,
i.e. no copy or move operations are performed.
The constructor of the element is called with exactly the same arguments,
as supplied to the function.
The
list::push_back
,
list::emplace_back
,
forward_list::push_back
and
forward_list::emplace_back
methods can be used to add an element to the end of a list (or construct one).
The
list::pop_back
,
methods can be used to remove the last element in the list.
The
list::push_front
,
list::emplace_front
,
list::pop_front
,
forward_list::push_front
,
forward_list::emplace_front
and
forward_list::pop_front
methods can respectively: add, construct and remove the first element in a list
or forward_list.
// not done 448 Some algorithms provided as methods for lists and forward_lists:
- the
list::merge
andforward_list::merge
can merge two sorted lists. No elements are copied, and the passed in container becomes empty after the merge. - the
list::splice
andforward_list::splice_after
methods transfer elements from one list to another. No elements are copied or moved, only the internal pointers of the list nodes are re-pointed. The elements are inserted respectively before and after the element pointed to by the passed in iterator . - the
list::sort
andforward_list::sort
methods sort the list elements while preserving the order of equivalent elements. Uses theoperator<
or the passed incompare
parameter, which has to satisfy theCompare
named requirement. - the
list::remove
,forward_list::remove
,list::remove_if
andforward_list::remove_if
methods remove all elements that are==
to the passed in value, or satisfy the passed inUnaryPredicate
parameter which must satisfy thePredicate
named requirement. - the
list::reverse
andforward_list::reverse
methods reverse the order of the elements in the list. No references or iterators become invalidated. - the
list::unique
andforward_list::unique
methods remove all consecutive duplicate elements from the list. Only the first element in each group of equal elements is left. Invalidates only the iterators and references to the removed elements.
Example:
#include <iostream>
#include <list>
int main() {
std::list<int> l({2, 3, 4, 4, 3, 4, 4, 4, 5, 6, 1, 1, 1, 1, 2});
for (const auto &val : l)
std::cout << val << " ";
std::cout << std::endl;
// prints: 2 3 4 4 3 4 4 4 5 6 1 1 1 1 2
l.unique();
for (const auto &val : l)
std::cout << val << " ";
std::cout << std::endl;
// prints: 2 3 4 3 4 5 6 1 2
return 0;
}
operation | cost |
---|---|
memory | \(\propto N\) (> vector) |
insertion and deletion at each point | constant |
reference and pointer invalidation on modification | NO ✅️ |
iterator invalidation on modification | NO ✅️ |
Use list
and forward_list
if the insertion and deletion operations not to
the front and back of the container are frequent.
The
map<K,V,C,A>
and
multimap<K,V,C,A>
structures are associative containers parametrized by
K
is the type of the keys.V
is the type of the values.C
is the type of the comparator of the keys (default=std::less<K>
).A
is the type of the allocator of the of the elements (default=std::allocator<std::pair<const K, V>>
).
The elements in a map
or multimap are std::pair<const K, V>
.
map
and multimap
structures are often implemented using
red-black trees
.
Why is std::map
implemented using a red black tree and not hash table ?
bcoz
(history and deadlines).
Example usage:
#include <iostream>
#include <map>
#include <string>
using namespace std::literals;
int main() {
// using an initializer_list<pair<const K, V>>
std::map<int, std::string> dep = {{2, "item 2"s},
{1, "item 1"s},
{3, "item 3"s},
{4, "item 4"s},
{5, "item 5"s}};
for (const auto &d : dep)
std::cout << d.second << "->" << d.first << std::endl;
std::cout << dep[4] << std::endl;
// prints:
// item 1->1
// item 2->2
// item 3->3
// item 4->4
// item 5->5
// item 4
return 0;
}
Two keys a
and b
are equal if !(a<b) && !(b<a)
\(\iff\)
!comp(a,b) && !comp(b,a)
. comp
has to be a strict order relation for
equality to be well defined this way.
Always use a strict order comparator in map
and multimap
, otherwise the
equality will not be well defined.
std::multimap is an associative container that contains a sorted list of key-value pairs, while permitting multiple entries with the same key.
Sorting is done according to the comparison function Compare, applied to the keys. Search, insertion, and removal operations have logarithmic complexity.
The order of the key-value pairs whose keys compare equivalent is the order of insertion and does not change.
The
std::map::operator[]
returns a reference to the value that is mapped to a key equivalent to key or
x respectively, performing an insertion if such key does not already exist. No
equivalent exists for a multimap
.
The
std::multimap::find
and
std::multimap::insert
methods are sometimes useful.
Useful map (and multimap) constructors:
- with no params: empty map.
- using an
initializer_list<pair<const K, V>>
. - using the cloning constructor.
- using two iterators: partial copy of range.
Inserting elements into a map<K,V>
(elements are pair<K,V>
).
- using the
std::map::operator[]
. - using the
std::map::insert
method (pass in apair<K,V>
instance). - using the
std::map::emplace
method (pass in the arguments of the constructor ofpair<K,V>
); constructs the element in-place in the map, saves the cost of a copy.
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>
using namespace std::literals;
int main() {
std::vector<std::pair<std::string, int>> nonsense({{"first item"s, 1},
{"second item"s, 2},
{"third item"s, 3},
{"fourth item"s, 4}});
// use override (7) for map insert
std::map<std::string, int> copnonsense(nonsense.begin(), nonsense.end());
for (const auto &val : copnonsense)
std::cout << val.first << ":" << val.second << std::endl;
// prints:
// first item:1
// fourth item:4
// second item:2
// third item:3
return 0;
}
- using the
std::map::merge
method merges two the passed inmap
into the support map, the passed in map is empty by the end. No elements are copied or moved, only the internal pointers of the container nodes are repointed. If there is an element in*this
with key equivalent to the key of an element from source, then that element is not extracted from source.
Access the elements in a map:
- using the
std::map::operator[]
method. ⚠️ if element does not exist, create it. - using the
std::map::at
method. If the element does not exist, throwsstd::out_of_range
exception. - using the
std::map::find
andstd::multimap::find
methods which finds an element by passed in key and returns an iterator pointing to the associated element ofend()
if not found. For amultimap
: if there are several elements with the requested key in the container, any of them may be returned. - using the
std::map::equal_range
andstd::multimap::equal_range
method returns a range containing all elements with the given key in the container. The range is defined by two iterators, one pointing to the first element that is not less than key and another pointing to the first element greater than key. Alternatively, the first iterator may be obtained withlower_bound()
, and the second withupper_bound()
. If there are no elements not less than key, past-the-end (see end()) iterator is returned as the first element. Similarly if there are no elements greater than key, past-the-end iterator is returned as the second element. - using a rang-based for loop ... duh.
#include <iostream>
#include <map>
int main(){
std::multimap<int, char> dict
{
{1, 'A'},
{2, 'B'},
{2, 'C'},
{2, 'D'},
{4, 'E'},
{3, 'F'}
};
auto range = dict.equal_range(2);
for (auto i = range.first; i != range.second; ++i)
std::cout << i->first << ": " << i->second << '\n';
// prints:
// 2: B
// 2: C
// 2: D
}
why does map
also have an equal_range
method if there can be no duplicate keys
? bcoz 🔮
(consistency and generic
usage of containers).
operation | cost |
---|---|
memory | \(\propto N\) (> list) |
element access time | \(\propto log_2(N)\) |
insertion and deletion EVERYWHERE | Constant |
reference and pointer invalidation on modification | NO ✅️ |
iterator invalidation on modification | NO ✅️ |
Careful with the memory consumption of map. If associative access is required
but log_2(N)
is too inefficient (lots of reads), favor an unordered_map
.
Also, map
and multimap
are unusable if no strict order relation can be
established among the would be keys.
An
unordered_map<K,V,H,C,A>
is an associative container that contains key-value pairs with unique keys.
Search, insertion, and removal of elements have average constant-time complexity.
An
unordered_multimap<K,V,H,C,A>
std::unordered_map is an associative container that contains key-value pairs
with unique keys. Search, insertion, and removal of elements have average
constant-time complexity.
Both are implemented with hash tables (contrast with RB-Trees for the ordered versions of theses data structures).
These classes are parametrised by 5 types:
K
the type of the keys.V
the type of the values.H
the type of the hash function;default=std::hash<K>
C
the type of the comparator indicating if two keys are equal;default=std::equal_to<K>
A
the type of the element allocator;default=std::allocator<std::pair<const K, V>>
The operator[]
is only defined for an unordered_map
; use
find
, insert
and equal_range
for an unordered_multimap
.
Constructors:
#include <iostream>
#include <string>
#include <unordered_map>
int main(){
std::unordered_map<std::string,int> a; // empty
a["item 1"] = 1;
a["item 2"] = 2;
a["item 3"] = 3;
for(const auto& k : a) std::cout << k.first << " " << k.second << std::endl;
// item 3 3
// item 2 2
// item 1 1
std::unordered_map<std::string,int> b(a);
for(const auto& k : b) std::cout << k.first << " " << k.second << std::endl;
// item 3 3
// item 2 2
// item 1 1
auto ib = a.begin();
ib++;
std::unordered_map<std::string,int> c(ib, a.end());
for(const auto& k : c) std::cout << k.first << " " << k.second << std::endl;
// item 1 1
// item 2 2
std::unordered_map<std::string, int> d = {{"nonsense 1", 1},{"nonsense 2", 2}};
for(const auto& k : d) std::cout << k.first << " " << k.second << std::endl;
// nonsense 2 2
// nonsense 1 1
return 0;
}
The difference between begin
and cbegin
?
🔮
begin
will return aniterator
or aconst_iterator
depending on the const-qualification of the object it is called on.cbegin
will return aconst_iterator
unconditionally. It's for flexibility. If you know you need aconst_iterator
, callcbegin
. If you know you need aniterator
, callbegin
and you'll get an error if it's not valid. If you don't care, callbegin
.
Definining a
custom hash function
.
If the H
(hash function-like), C
(equal comparator) or A
(allocator) types
are specified in the unordered_map<...>
template parameters, provide
the bucket_count as first parameter to the constructor, and instances of the
specified type to be used:
// Option 3: Use lambdas
// Note that the initial bucket count has to be passed to the constructor
struct Goo { int val; };
auto hash = [](const Goo &g){ return std::hash<int>{}(g.val); };
auto comp = [](const Goo &l, const Goo &r){ return l.val == r.val; };
std::unordered_map<Goo, double, decltype(hash), decltype(comp)> m8(10, hash, comp);
See
unordered_map::unordered_map
constructor example on cppreference for more details.
The main methods used to insert or add elements in an
unordered_map
or
unordered_multimap
are:
- the
operator[k]
which returns a reference to the element with keyk
or creates it if does not exist. - the
insert
method which inserts an element (or elements) and returns a pair containing an iterator pointing to the inserted element and a boolean indicating whether or not the insertion succeded. - the
insert_or_assign
method which inserts an element or assigns it if it already exist. Still no idea what the difference is betweeninsert_or_assign
andoperator[]
.
The main methods to access elements in an
unordered_map
or
unordered_multimap
are:
at
method; returns a reference to the element at the specified key throws anstd::out_of_range
exception if element does not exist.- the
operator[k]
which returns a reference to the element with keyk
or creates it if does not exist. - by dereferencing an iterator pointing to some
unordered_map
element. UB if invalid. - using a range based for loop.
NO FRONT AND BACK METHODS : unordered, it's in the name.
operation | cost |
---|---|
memory | \(\propto N\) (> map) |
element access time | Constant |
insertion and deletion EVERYWHERE | Constant |
reference and pointer invalidation on modification | NO ✅️ |
iterator invalidation on modification | YES ⚠️ |
The
set<K,C,A>
and
multiset<K,C,A>
are associative containers that contain sorted sets of objects of type K.
Contrary to set
, multiset
allows duplicate keys.
These generic classes are parametrised by 5 types:
K
the type of the keys.C
the type of the comparator indicating if two keys are equal;default=std::less<K>
A
the type of the element allocator;default=std::allocator<K>
They are usually represented as
red-black trees
.
These two structures have an interface very similar to map
and multimap
.
(except no Values associated with keys,
set::find
returns an iterator pointing to a key, while
map::find
return an iterator pointing to a pair<key, value>
).
operation | cost |
---|---|
memory | \(\propto N\) (> list) |
element access time | \(\propto log_2(N)\) |
insertion and deletion EVERYWHERE | Constant |
reference and pointer invalidation on modification | NO ✅️ |
iterator invalidation on modification | NO ✅️ |
The
unordered_set<K,C,A>
and
unordered_multiset<K,C,A>
are associative containers that contain sets of objects of type K.
Contrary to set
, multiset
allows duplicate keys.
These generic classes are parametrised by 5 types:
K
the type of the keys.H
the type of the hash function;default=std::hash<K>
C
the type of the comparator indicating if two keys are equal;default=std::equal_to<K>
A
the type of the element allocator;default=std::allocator<K>
Both are implemented with hash tables (contrast with RB-Trees for the ordered versions of theses data structures).
These two structures have an interface very similar to unordered_map
and unordered_multimap
.
(except no Values associated with keys,
unordered_set::find
returns an iterator pointing to a key, while
unordered_map::find
return an iterator pointing to a pair<key, value>
).
operation | cost |
---|---|
memory | \(\propto N\) (> map) |
element access time | Constant |
insertion and deletion EVERYWHERE | Constant |
reference and pointer invalidation on modification | NO ✅️ |
iterator invalidation on modification | YES ⚠️ |
std::stack<T,Cont>
is a LIFO
(last-in, first-out) data structure.
It is a generic class parametrized by two types:
T
is the type of the stored elements.Cont
is the type of the container used to implement the stack. (default = std::deque<T>
)
Notice that std::stack
is not a container, rather it is a
container adaptor
.
Use a container that provides efficient access, insertion and removal at the
back (vector
, list
, string
and especially deque
); it has to provide
the back
, pushback
and popback
methods.
Difference between deque
and list
implementations:
🔮
There a three remarkable constructors for a stack
:
- with no params: just call the default constructor of the underlying container.
- with a container instance.
- by cloning another stack with the same type.
Inserting elements to the top of the stack (ie: back of the underlying container)
is done with the
push
and
emplace
methods
(call the push_back
and emplace_back
methods of the underlying container).
Use the
top
method to get a
reference to top of the stack.
Use the
pop
method to remove
the top of the stack. (RETURNS void, calls the pop_back
method of the underlying
container).
operation | cost |
---|---|
memory | \(\propto N\) |
top element access time | Constant |
insertion and deletion at the top | Constant |
reference and pointer invalidation on modification | depends on the underlyning container |
iterator invalidation on modification | NO ITERATORS |
std::queue<T,Cont>
class is a container adaptor that gives the functionality of a queue
- specifically, a FIFO (first-in, first-out) data structure.
It is a generic class parametrized by two types:
T
is the type of the stored elements.Cont
is the type of the container used to implement the queue. (default = std::deque<T>
)
The class template acts as a wrapper to the underlying container - only a specific set of functions is provided. The queue pushes the elements on the back of the underlying container and pops them from the front.
Often used to implement buffers.
Use list
or (default) deque
as the underlying container: NOT vector
.
There a three remarkable constructors for a queue
:
- with no params: just call the default constructor of the underlying container.
- with a container instance.
- by cloning another queue with the same type.
Use the
push
and
emplace
methods to add an element to the end of the queue.
Use the
pop
method to remove the first element in the queue.
Use the
back
and
front
methods to access the last and first element in the queue respectively (reference).
std::priority_queue<T,Cont,Comp>
is a container adaptor that provides constant time lookup of the largest
(by default) element, at the expense of logarithmic insertion and extraction.
A user-provided Compare can be supplied to change the ordering, e.g. using
std::greater<T>
would cause the smallest element to appear as the top().
Working with a priority_queue is similar to managing a heap in some random access container, with the benefit of not being able to accidentally invalidate the heap.
The default container is vector
, though any container providing fast back
deletion/insertion and fast front access is adequate (deque
also workds,
but more memory is consumed).
There a three remarkable constructors for a priority_queue
:
- with no params: just call the default constructor of the underlying container.
- with a container and comparator instances.
- by cloning another priority_queue with the same type.
The
push
inserts element and sorts the underlying container.
emplace
constructs element in-place and sorts the underlying container.
The
pop
method to remove the top element from the priority_queue.
The
top
returns a (const) reference to the top element in the priority queue.
operation | cost |
---|---|
memory | \(\propto N\) |
top element access time | Constant |
insertion and removal at the top | \(\propto log(N)\) |
The class template
std::span<T,Extent>
describes an object that can refer to a contiguous
sequence of objects with the first element of the sequence at position zero.
A span can either have a static extent, in which case the number of elements
in the sequence is known at compile-time and encoded in the type, or a dynamic
extent.
For a span s
, pointers, iterators, and references to elements of s
are
invalidated when an operation invalidates a pointer in the range
[s.data(), s.data() + s.size()]
.
constructors:
- from a another span
- from a standard array whose size is known at compile time or an
array<T>
, not arrays passed to functions (those are pointers). - from a pair of iterators
- from an iterator and a number of elements
element access:
- iterators
front
andback
methods, which return references to the first and last elements of the span.operator[]
returns a reference to an element at the given index, UB if the index is incompatible with the range. (there is noat
method !!!).first(n)
which returns a span containing the firstn
elements of the span.last(n)
which returns a span containing the lastn
elements of the span.subspan(offset, count)
which returns a span containingcount
elements starting at positionoffset
in the span.
The class template
basic_string_view<CharT, Traits>
describes an object that can refer to a constant contiguous sequence of
CharT
with the first element of the sequence at position zero.
For a basic_string_view str, pointers, iterators, and references to elements
of str are invalidated when an operation invalidates a pointer in the range
[str.data(), str.data() + str.size())
.
constructors:
- from another
string_view
, (cloning constructor). - from a C-style string.
- from a C++ string.
- from a pointer to a block of characters and a count.
- from an interval of characters delimited by two iterators.
element access:
- iterators
front
andback
methods, which return const references to the first and last characters of the string_view.operator[]
returns a const reference to a character at the given index, UB if the index is incompatible with the range.at()
returns a const reference to the character at specified location pos. Bounds checking is performed, exception of typestd::out_of_range
will be thrown on invalid access.substring(offset, count)
which returns a string_view containingcount
characters starting at positionoffset
in the string_view.data()
Returns a pointer to the underlying character array. The pointer is such that the range[data(), data() + size())
is valid and the values in it correspond to the values of the view.
other useful methods:
remove_prefix(n)
moves the start of the view forward byn
characters. The behavior is undefined ifn
>size()
.remove_suffix(n)
moves the end of the view back byn
characters. The behavior is undefined ifn
>size()
.swap(v)
exchanges the view with that ofv
.operator=(view)
replaces the view with that ofview
.starts_with(prefix)
checks if the string view begins with the given prefix.ends_with(suffix)
checks if the string view ends with the given suffix.find(v, pos)
finds the first substring equal to the given character sequence. Finds the first occurence ofv
in this view, starting at positionpos
.rfind(v, pos)
finds the last substring equal to the given character sequence. Finds the last occurence ofv
in this view, starting at positionpos
.- the
==
,!=
,<
,<=
,>
,>=
operators perform lexicographical comparisons between two string views. (can send a string view to a stream with<<
). - use the
"blah"sv
to declare a string view literal. (using namespace std::literals;).
Favor using string_view
over string
. Substring extraction is done in linear
time for string
due to the copy. It is done in constant time for
string_view
because no copy is performed.
The Ranges Library (C++20)
A
Range
is any iterable collection of data. It must provide an begin and end
iterator. There are multiple range categories (C++20 concepts),
some of which are listed below:
ranges::range
: collection providing a begin and end iterator.ranges::sized_range
: collection whose size can be determined in constant time.ranges::input_range
: collection iterable through an input_iterator.ranges::output_range
: collection iterable through an output_iterator.ranges::forward_range
: collection iterable through a forward_iterator.ranges::bidirectional_range
: collection iterable through a bidirectional_iterator.ranges::random_access_range
: collection iterable through a random_access_iterator.ranges::viewable_range
: collection that can be converted to a view (throughviews::all
).
Views are iterable entities that can access a range's data and process it. They are lazy evaluated at iteration. Some common C++ views are listed below:
std::ranges::views::filter
: filters elements that satisfy a predicate.std::ranges::views::transform
: applies a function on all elements (functional style map).std::ranges::views::all
: returns all elements.std::ranges::views::take
: returns N first elements.std::ranges::views::drop
: return all but the N first elements.std::ranges::views::take_while
: returns all elements until a specified predicate is no longer satisfied.std::ranges::views::join
: view consisting of the sequence obtained from flattening a view of ranges. (Eg: vector<vector> -> flat sequence) std::ranges::views::counted
: return n elements starting at iterator i.std::ranges::views::reverse
: iterate through elements in reverse order.std::ranges::views::elements
: accepts a view of tuple-like values, issues a view consisting of the Nth element of each tuple.std::ranges::views::keys
: accepts a view of tuple-like values, issues a view consisting of the 1st element of each tuple.std::ranges::views::values
: accepts a view of tuple-like values, issues a view consisting of the second element of each tuple.
Views can be composed, left to right with the overloaded operator|
.
std::vector<int> v = {1,2,3,4,5,6,7,8};
auto oddSquaredDrop2 = v | std::views::filter([](int n){return n%2!=0;})
| std::views::drop(2)
| std::views::transform([](int n){return n*n;});
for(int i : oddSquaredDrop2) // prints: 25\n49
std::cout << i << std::endl;
Some views produce data themselves: these are the so-called range-factories.
std::ranges::istream_view
: range factory that generates a sequence of elements by repeatedly callingoperator>>
.std::ranges::iota
: range factory that generates a sequence of elements by repeatedly incrementing an initial value. Can be either bounded or unbounded (infinite).
/** text.txt:
this a serious corporate document that
needs to conform to very serious
guidelines
*/
#include <iostream>
#include <fstream>
#include <ranges>
int main(){
std::ifstream s("text.txt");
auto all = std::ranges::istream_view<std::string>(s)
|std::ranges::views::transform([](std::string s){return "goofy_"+s;});
for(auto x : all)
std::cout << x << ' ';
std::cout << std::endl;
return 0;
}
/** prints:
goofy_this goofy_a goofy_serious goofy_corporate goofy_document goofy_that goofy_needs goofy_to goofy_conform goofy_to goofy_very goofy_serious goofy_guidelines
*/
Algorithms
Iterators types:
- Contiguous Iterators.
- Random Access Iterators.
- Bidirectional Iterators.
- Forward Iterators.
- Input Iterators.
- Output Iterators.
Adapter Iterators:
- Reverse Iterators.
- Move Iterators.
- Back Inserter Iterators.
- Front Inserter Iterators.
Consult cppreference for details on iterator types, either as named requirements or precise classes.
List of useful algorithms; again, consult cppreference for up-to-date descriptions.
Non modifying algorithms:
all_of
(input/forward iterators): check if all elements satisfy the supplied predicate.any_of
(input/forward iterators): check if any elements satisfy the supplied predicate.none_of
(input/forward iterators): check if no elements satisfy the supplied predicate.for_each
(input/forward iterators): apply function to all elements.for_each_n
(input/forward iterators): apply function to the first n elements.count
(input/forward iterators): count the number of elements equal to the supplied element.count_if
(input/forward iterators): count the number of elements that satisfy the supplied predicate.mismatch
(input/forward iterators): return pair of iterators to the first mismatched (unequal) elements in the supplied ranges.find
(input/forward iterators): return iterator pointing to the first element in the range equal to the supplied element.find_if
(input/forward iterators): return iterator pointing to the first element in the range that satisfies the supplied predicate.find_if_not
(input/forward iterators)
🔮