49
Standard Library II Standard Library II 488

StandardLibraryII - db.in.tum.dedb.in.tum.de/teaching/ss19/c++praktikum/slides/lecture-09.pdfStandardLibraryII FunctionObjects FunctionObjects(1) RegularfunctionsarenotobjectsinC++

  • Upload
    vankien

  • View
    226

  • Download
    0

Embed Size (px)

Citation preview

Standard Library II

Standard Library II

488

Standard Library II Function Objects

Function Objects (1)

Regular functions are not objects in C++

• Cannot be passed as parameters• Cannot have state• …

C++ additionally defines the FunctionObject named requirement. For a type T tobe a FunctionObject• T has to be an object• operator()(args) has to be defined for T for a suitable argument list args

which can be empty• Often referred to as functors

489

Standard Library II Function Objects

Function Objects (2)

There are a number of valid function objects defined in C++

• Pointers to functions• Lambda expressions• Stateful function objects in form of classes

Functions and function references are not function objects• Can still be used in the same way due to implicit function-to-pointer

conversion

490

Standard Library II Function Objects

Function Pointers (1)

While functions are not objects they do have an address• Location in memory where the actual assembly code resides• Allows declaration of function pointers

Function pointers to non-member functions• Declaration: return-type (*identifier)(args)• Allows passing functions as parameters

• E.g. passing a custom compare function to std::sort (see later)• E.g. passing a callback to a method

• Can be invoked in the same way as a function

491

Standard Library II Function Objects

Function Pointers (2)

Example

int callFunc(int (*func)(int, int), int arg1, int arg2) {return (*func)(arg1, arg2);

}//--------------------------------------------------------double callFunc(double (*func)(double), double argument) {

return func(argument); // Automatically dereferenced}//--------------------------------------------------------int add(int arg1, int arg2) { return arg1 + arg2; }double add4(double argument) { return argument + 4; }//--------------------------------------------------------int main() {

auto i = callFunc(add, 2, 4); // i = 6auto j = callFunc(&add4, 4); // j = 8, "&" can be omitted

}

492

Standard Library II Function Objects

Lambda Expressions (1)

Function pointers can be unwieldy• Function pointers cannot easily capture environment• Have to pass all variables that affect function by parameter• Cannot have “local” functions within other functions

C++ defines lambda expressions as a more flexible alternative• Lambda expressions construct a closure• Closures store a function together with an environment• Lambda expressions can capture variables from the scope where they are

defined

493

Standard Library II Function Objects

Lambda Expressions (2)

Lambda expression syntax• [ captures ] ( params ) -> ret { body }• captures specifies the parts of the environment that should be stored• params is a comma-separated list of function parameters• ret specifies the return type and can be omitted, in which case the return

type is deduced from return statements inside the body

The list of captures can be empty• Results in stateless lambda expression• Stateless lambda expressions are implicitly convertible to function pointers

Lambda expressions have unique unnamed class type• Have to use auto when assigning lambda expressions to variables• Declaration of a lambda variable (e.g. as member) is not possible

494

Standard Library II Function Objects

Lambda Expressions (3)

Example

int callFunc(int (*func)(int, int), int arg1, int arg2) {return func(arg1, arg2);

}//--------------------------------------------------------int main() {

auto lambda = [](int arg1, int arg2) {return arg1 + arg2;

};

int i = callFunc(lambda, 2, 4); // i = 6int j = lambda(5, 6); // j = 11

}

495

Standard Library II Function Objects

Lambda Expressions (4)

All lambda expressions have unique types

// ERROR: Compilation will fail due to ambiguous return typeauto getFunction(bool first) {

if (first) {return []() {

return 42;};

} else {return []() {

return 42;};

}}

496

Standard Library II Function Objects

Lambda Captures (1)

Lambda captures specify what constitutes the state of a lambda expression• Can refer to automatic variables in the surrounding scopes (up to the

enclosing function)• Can refer to the this pointer in the surrounding scope (if present)

Captures can either capture by-copy or by-reference• Capture by-copy creates a copy of the captured variable in the lambda state• Capture by-reference creates a reference to the captured variable in the

lambda state• Captures can be used in the lambda expression body like regular variables or

references

497

Standard Library II Function Objects

Lambda Captures (2)

Lambda captures are provided as a comma-separated list of captures• By-copy: identifier or identifier initializer• By-reference: &identifier or &identifier initializer• identifier must refer to automatic variables in the surrounding scopes• identifier can be used as an identifier in the lambda body• Each variable may be captured only once

First capture can optionally be a capture-default• By-copy: =• By-reference: &• Allows any variable in the surrounding scopes to be used in the lambda body• Specifies the capture type for all variables without explicit captures• If present, only diverging capture types can be specified afterwards

498

Standard Library II Function Objects

Lambda Captures (3)

Capture types

int main() {int i = 0;int j = 42;

auto lambda1 = [i](){}; // i by-copyauto lambda2 = [&i](){}; // i by-reference

auto lambda2 = [&, i](){}; // j by-reference, i by-copyauto lambda3 = [=, &i](){}; // j by-copy, i by-reference

auto lambda4 = [&, &i](){}; // ERROR: non-diverging capture typesauto lambda5 = [=, i](){}; // ERROR: non-diverging capture types

}

499

Standard Library II Function Objects

Lambda Captures (4)

Capture by-copy vs. by-reference

int main() {int i = 42;

auto lambda1 = [i]() { return i + 42; };auto lambda2 = [&i]() { return i + 42; };

i = 0;

int a = lambda1(); // a = 84int b = lambda2(); // b = 42

}

500

Standard Library II Function Objects

Lambda Captures (5)

We can also capture a this pointer• By-copy: *this (actually copies the current object)• By-reference: this

struct Foo {int i = 0;

void bar() {auto lambda1 = [*this]() {return i + 42; };auto lambda2 = [this](){ return i + 42; };

i = 42;

int a = lambda1(); // a = 42int b = lambda2(); // b = 84

}};

501

Standard Library II Function Objects

Lambda Captures (6)

By-copy capture-default copies only the this pointer

struct Foo {int i = 0;

void bar() {auto lambda1 = [&]() {return i + 42; };auto lambda2 = [=](){ return i + 42; };

i = 42;

int a = lambda1(); // a = 84int b = lambda2(); // b = 84

}};

502

Standard Library II Function Objects

Lambda Captures (7)

Beware of lifetimes when capturing

#include <memory>

int main() {auto ptr = std::make_unique<int>(4);

auto f2 = [inner = ptr.get()]() {return *inner;

};

int a = f2(); // 4ptr.reset();int b = f2(); // undefined behavior

}

By-reference capture can also easily lead to dangling references

503

Standard Library II Function Objects

Stateful Function Objects (1)

Situation so far• Functions are generally stateless• State has to be kept in surrounding object, e.g. class instances• Lambda expressions allow limited state-keeping

Function objects can be implemented in a regular class• Allows the function object to keep arbitrary state• Difference to lambda expressions: State can be changed during lifetime

504

Standard Library II Function Objects

Stateful Function Objects (2)Example

struct Adder {int value;

int operator()(int param) {return param + value;

}};//--------------------------------------------------------int main() {

Adder myAdder;myAdder.value = 1;myAdder(1); // 2myAdder(4); // 5myAdder.value = 5;myAdder(1); // 6

}

505

Standard Library II Function Objects

std::function (1)

std::function is a general purpose wrapper for all callable targets• Defined in the <functional> header• Able to store, copy and invoke the wrapped target• Potentially incurs dynamic memory allocations• Often adds unnecessary overhead• Should be avoided where possible

#include <functional>//--------------------------------------------------------int add2(int p){ return p + 2; }//--------------------------------------------------------int main() {

std::function<int(int)>adder = add2;int a = adder(5); // a = 7

}

506

Standard Library II Function Objects

std::function (2)

Potential std::function use case

#include <functional>//--------------------------------------------------------std::function<int()> getFunction(bool first){

int a = 14;

if (first)return [=]() { return a; };

elsereturn [=]() { return 2 * a; };

}//--------------------------------------------------------int main() {

return getFunction(false)() + getFunction(true)(); // 42}

507

Standard Library II Function Objects

Working with Function ObjectsCode that intends to call function objects should usually rely on templates

int bad(int (*fn)()) { return fn(); }//--------------------------------------------------------template <typename Fn>int good(Fn&& fn) { return fn(); }//--------------------------------------------------------struct Functor {

int operator()() { return 42; }};//--------------------------------------------------------int main() {

Functor ftor;

bad([]() { return 42; }); // OKbad(ftor); // ERROR

good([]() { return 42; }); // OKgood(ftor); // OK

}

508

Standard Library II The Algorithms Library

The Algorithms Library

The algorithms library is part of the C++ standard library• Defines operations on ranges of elements [first, last)• Bundles functions for sorting, searching, manipulating, etc.• Ranges can be specified using pointers or any appropriate iterator type• Spread in 4 headers

• <algorithm>• <numeric>• <memory>• <cstdlib>

• We will focus on <algorithm> as it bundles the most relevant parts

509

Standard Library II The Algorithms Library

std::sort

Sorts all elements in a range [first, last) in ascending order• void sort(RandomIt first, RandomIt last);• Iterators must be RandomAccessIterators• Elements have to be swappable (std::swap or user-defined swap)• Elements have to be move-assignable and move-constructible• Does not guarantee order of equal elements• Needs O(n ∗ log(N)) comparisons

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<unsigned> v = {3, 4, 1, 2};std::sort(v.begin(), v.end()); // 1, 2, 3, 4

}

510

Standard Library II The Algorithms Library

Custom Comparison Functions

Sorting algorithms can be modified through custom comparison functions• Supplied as function objects (Compare named requirement)• Have to establish a strict weak ordering on the elements• Syntax: bool cmp(const Type1 &a, const Type2 &b);• Return true if and only if a < b according to some strict weak ordering <

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<unsigned> v = {3, 4, 1, 2};std::sort(v.begin(), v.end(), [](unsigned lhs, unsigned rhs) {

return lhs > rhs;}); // 4, 3, 2, 1

}

511

Standard Library II The Algorithms Library

Further Sorting Operations

Sometimes std::sort may not be the optimal choice• Does not necessarily keep order of equal-ranked elements• Sorts the entire range (unnecessary e.g. for top-k queries)

Keep the order of equal-ranked elements• std::stable_sort

Partially sort a range• std::partial_sort

Check if a range is sorted• std::is_sorted• std::is_sorted_until

512

Standard Library II The Algorithms Library

Searching

The algorithms library offers a variety of searching operations• Different set of operations for sorted and unsorted ranges• Searching on sorted ranges is faster in general• Sorting will pay off for repeated lookups

Arguments against sorting• Externally prescribed order that may not be modified• Frequent updates or insertions

General semantics• Search operations return iterators pointing to the result• Unsuccessful operations are usually indicated by returning the last iterator

of a range [first, last)

513

Standard Library II The Algorithms Library

Searching - Unsorted

Find the first element satisfying some criteria• std::find• std::find_if• std::find_if_not

Search for a range of elements in another range of elements• std::search

Count matching elements• std::count• std::count_if

Many more useful operations (see reference documentation)

514

Standard Library II The Algorithms Library

std::find

Example

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {2, 6, 1, 7, 3, 7};

auto res1 = std::find(vec.begin(), vec.end(), 7);int a = std::distance(vec.begin(), res1); // 3

auto res2 = std::find(vec.begin(), vec.end(), 9);assert(res2 == vec.end());

}

515

Standard Library II The Algorithms Library

std::find_if

Example

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {2, 6, 1, 7, 3, 7};

auto res1 = std::find_if(vec.begin(), vec.end(),[](int val) { return (val % 2) == 1; });

int a = std::distance(vec.begin(), res1); // 2

auto res2 = std::find_if_not(vec.begin(), vec.end(),[](int val) { return val <= 7; });

assert(res2 == vec.end());}

516

Standard Library II The Algorithms Library

Searching - Sorted

On sorted ranges, binary search operations are offered• Complexity O(log(N)) when range is given as RandomAccessIterator• Can employ custom comparison function (see above)

When called with ForwardIterators complexity is linear in number ofiterator increments

Search for one occurrence of a certain element• std::binary_search

Search for range boundaries• std::lower_bound• std::upper_bound

Search for all occurrences of a certain element• std::equal_range

517

Standard Library II The Algorithms Library

std::binary_search

Lookup an element in a range [first, last)• Only checks for containment, therefore return type is bool• To locate the actual values use std::equal_range

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};

auto res1 = std::binary_search(v.begin(), v.end(), 3);assert(res1 == true);

auto res2 = std::binary_search(v.begin(), v.end(), 0);assert(res2 == false);

}

518

Standard Library II The Algorithms Library

std::lower_bound

Returns iterator pointing to the first element >= the search value

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};

auto res1 = std::lower_bound(v.begin(), v.end(), 3);int a = std::distance(v.begin(), res1); // 3

auto res2 = std::lower_bound(v.begin(), v.end(), 0);int b = std::distance(v.begin(), res2); // 0

}

519

Standard Library II The Algorithms Library

std::upper_bound

Returns iterator pointing to the first element > the search value

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};

auto res1 = std::upper_bound(v.begin(), v.end(), 3);int a = std::distance(v.begin(), res1); // 6

auto res2 = std::upper_bound(v.begin(), v.end(), 4);assert(res2 == v.end());

}

520

Standard Library II The Algorithms Library

std::equal_range

Locates range of elements equal to search value• Returns pair of iterators (begin and end of range)• Identical to using std::lower_bound and std::upper_bound

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};

auto [begin1, end1] = std::equal_range(v.begin(), v.end(), 3);int a = std::distance(v.begin(), begin1); // 3int b = std::distance(v.begin(), end1); // 6

auto [begin2, end2] = std::equal_range(v.begin(), v.end(), 0);assert(begin2 == end2);

}

521

Standard Library II The Algorithms Library

Permutations

The algorithms library offers operations to permute a given range• Can iterate over permutations in lexicographical order• Requires at least BidirectionalIterators• Values have to be swappable• Order is determined using operator< by default• A custom comparison function can be supplied (see above)

Initialize a dense range of elements• std::iota

Iterate over permutations in lexicographical order• std::next_permutation• std::prev_permutation

522

Standard Library II The Algorithms Library

std::iota

Initialize a dense range of elements• std::iota(ForwardIt first, ForwardIt last, T value)• Requires at least ForwardIterators• Fills the range [first, last) with increasing values starting at value• Values are incremented using operator++()

#include <numeric>#include <memory>//--------------------------------------------------------int main() {

auto heapArray = std::make_unique<int[]>(5);std::iota(heapArray.get(), heapArray.get() + 5, 2);

// heapArray is now {2, 3, 4, 5, 6}}

523

Standard Library II The Algorithms Library

std::next_permutation

Reorders elements in a range to the lexicographically next permutation• bool next_permutation(BidirIt first, BidirIt last)• Returns false if the current permutation was the lexicographically last

permutation (the range is then sorted in ascending order)

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {1, 2, 3};

bool b = std::next_permutation(v.begin(), v.end());// b == true, v == {1, 3, 2}b = std::next_permutation(v.begin(), v.end());// b == true, v == {2, 1, 3}

}

524

Standard Library II The Algorithms Library

std::prev_permutation

Reorders elements in a range to the lexicographically previous permutation• bool prev_permutation(BidirIt first, BidirIt last)• Returns false if the current permutation was the lexicographically first

permutation (the range is then sorted in descending order)

#include <algorithm>#include <vector>//--------------------------------------------------------int main() {

std::vector<int> v = {1, 3, 2};

bool b = std::prev_permutation(v.begin(), v.end());// b == true, v == {1, 2, 3}b = std::prev_permutation(v.begin(), v.end());// b == false, v == {3, 2, 1}

}

525

Standard Library II The Algorithms Library

Additional Functionality

The algorithms library offers many more operations• std::min & std::max over a range instead of two elements• std::merge & std::in_place_merge for merging of sorted ranges• Multiple set operations (intersection, union, difference, …)• Heap functionality• Sampling of elements using std::sample• Swapping elements using std::swap• Range modifications

• std::copy To copy elements to new location• std::rotate To rotate range• std::shuffle To randomly reorder elements

• For even more operations: See the reference documentation

526

Standard Library II The Random Library

The Random Library

The random library defines pseudo-random number generators and distributions• Defined in <random> header• Bundles several useful components

• Abstraction for random devices• Random number generators• Wrappers to generate numerical distributions from RNGs

Should always be preferred over functionality from <cstdlib> header• rand produces very low-quality random numbers• E.g. in one example the lowest bit simply alternates between 0 and 1• Especially serious if rand is used with modulo operations

527

Standard Library II The Random Library

Random Number Generators (1)

The random library defines various pseudo-random number generators• Uniform pseudo-random bit generators with distinct properties• RNGs can be seeded and reseeded• RNGs can be equality-compared• RNGs are not thread-safe• Usually, one should prefer the Mersenne Twister generators

The random library additionally defines a default_random_engine type alias• Implementation is implementation-defined

Do not use if you want portability

Most RNGs are template specializations of an underlying random number engineAlways use the predefined RNGs unless you know exactly what you are doing

528

Standard Library II The Random Library

Random Number Generators (2)

Mersenne Twister engine• Predefined for 32-bit (std::mt19937) and 64-bit (std::mt19937_64)

output width• Produces high-quality unsigned random numbers in [0, 2w − 1] where w is the

number of bits• Can and should be seeded in the constructor

#include <cstdint>#include <random>//--------------------------------------------------------int main() {

std::mt19937 engine(42);

unsigned a = engine(); // a == 1608637542unsigned b = engine(); // b == 3421126067

}

529

Standard Library II The Random Library

std::random_device

Standard interface to every available source of external randomness• /dev/random, atmospheric noise, …• Actual sources are implementation dependent• Only “real” source of randomness

Can degrade to a pseudo-random number generator when no source of truerandomness is available

#include <cstdint>#include <random>//--------------------------------------------------------int main() {

std::mt19937 engine(std::random_device()());

unsigned a = engine(); // a == ???unsigned b = engine(); // b == ???

}

530

Standard Library II The Random Library

Distributions

Random number generators are rather limited• Fixed output range• Fixed output distribution (approximately uniform)

The random library provides distributions to transform the output of RNGs• All distributions can be combined with all random engines• Various well-known distributions are provided

• Uniform• Normal• Bernoulli• Possion• …

• Some distributions are available as discrete or continuous distributions

531

Standard Library II The Random Library

std::uniform_int_distribution

Generates discrete uniform random numbers in range [a, b]• Integer type specified as template parameter• Constructed as uniform_int_distribution<T>(T a, T b)• If not specified a defaults to 0 and b to the maximum value of T• Numbers generated by operator(Generator& g)() where g is any random

number generator

#include <random>//--------------------------------------------------------int main() {

std::mt19937 engine(42);std::uniform_int_distribution<int> dist(-2, 2);

int d1 = dist(engine); // d1 == -1int d2 = dist(engine); // d2 == -2

}

532

Standard Library II The Random Library

std::uniform_real_distribution

Generates continuous uniform random numbers in range [a, b]• Floating point type specified as template parameter• Constructed as uniform_real_distribution<T>(T a, T b)• If not specified a defaults to 0 and b to the maximum value of T• Numbers generated by operator(Generator& g)() where g is any random

number generator

#include <random>//--------------------------------------------------------int main() {

std::mt19937 engine(42);std::uniform_real_distribution<float> dist(-2, 2);

float d1 = dist(engine); // d1 == -0.50184float d2 = dist(engine); // d2 == 1.18617

}

533

Standard Library II The Random Library

Seeding

Random generators should generate new random numbers each time• The seed value of a generator is used to calculate all other random numbers• Normally the seed should itself be a random number, e.g. by random_device• Deterministic sequences are preferable e.g. for tests or experiments• For tests or experiments seed can be fixed to an arbitrary integer

Entropy of a generator is entirely dependent on the entropy of the seedgenerator

534

Standard Library II The Random Library

Generating Random Dice Rolls

Example

#include <random>//--------------------------------------------------------int main() {

// Use random device to seed generatorstd::random_device rd;// Use pseudo-random generator to get random numbersstd::mt19937 engine(rd());// Use distribution to generate dice rollsstd::uniform_int_distribution<> dist(1, 6);

int d1 = dist(engine); // gets random dice rollint d2 = dist(engine); // gets random dice roll

}

535

Standard Library II The Random Library

Problems With Modulo

Modulo should in general not be used to limit the range of RNGs• Most random number generators generate values in [0, 2w − 1] for some w• When using modulo with a number that is not a power of two modulo will

favor smaller values

Consider e.g. random dice rolls• Assume a perfect random generator gen with w = 3

• gen will produce all values in {0, . . . , 7} with equal probability 0.125

int randomDiceroll() {return gen() % 6 + 1;

}

• P(randomDiceroll() = x) = 0.25 for x ∈ {1, 2}• P(randomDiceroll() = x) = 0.125 for x ∈ {3, 4, 5, 6}

536