Cvičení 12 - C++11

Úvod do cvičení

Na tomto cvičení se studenti budou seznamovat s některými aspekty nových věcí v C++11. Konkrétně se bude jednat o constexpr funkce, lambda funkce a range-for cyklus. Tyto tři oblasti jsou každá zajímavá něčím jiným a zároveň není třeba mít znalosti navíc v případě použití (jak bychom mohli zjistit, kdybychom si hráli vlákny nebo atomickými proměnnými).

Pro práci na tomto cvičení potřebujete jeden z těchto kompilátorů (úlohy na nich byly testovány)

  • GCC 4.8.2 (nebo novější)
  • clang 3.3 (nebo novější)
  • MSVC ve verzi dodávané s Visual Studio 2013 (nebo novější)

Pokud budete k překladu používat GCC (příkaz g++) nebo clang (příkaz clang++), je nutné překladači sdělit, že má použít standard C++11 pomocí přepínače -std=c++11.

První úkol - constexpr funkce

Funkce, kterou označíme jako constexpr, je překladač povinen (pokud lze) vypočítat během překladu a na místo volání v kódu dosadit již vypočtenou hodnotu. Z tohoto důvodu mají constexpr funkce ve standardu C++11 mnohá omezení. Například tělo funkce se smí skládat v podstatě pouze z příkazu return, který může obsahovat zase jenom volání constexpr funkcí, případně elementární příkazy (aritmetické operátory, ternární operátor, bitové operátory, porovnání, …).

Označení constexpr mohou mít nejen funkce, ale i konstruktory a proměnné. Vzhledem k jisté netriviálnosti následných požadavků na použité výrazy se jimi na tomto cvičení nebudeme zabývat. Zájemci si mohou detaily nastudovat na cppreference.com

.

Tento kód lze kompilovat pomocí překladačů GCC 4.8.2 a clang 3.3. Lze také použít Visual Studio, je ovšem nutné stáhnout z webu Microsoftu beta verzi překladače.

Zadání úkolu

Vaším úkolem je implementovat těla následujících funkcí. Svůj kód si ověříte překladem – chybný výpočet zastaví překlad.

  • Faktoriál – funkce pro výpočet faktoriálu, standardní rekurzivní varianta.
  • Fibonacciho posloupnost – funkce pro výpočet n-tého Fibonacciho čísla, standardní rekurzivní varianta.
  • Fibonacciho posloupnost2 – funkce pro výpočet n-tého Fibonacciho čísla, použijte variantu s lineárním počtem rekurzivního zanoření. Bonusový úkol.
  • Součet řady čísel – funkce pro výpočet součtu jednoduché řady čísel.
/**
 * Compute n-th number of factorial series.
 * @param n
 * @return n-th number
 */
constexpr int fact(int n) {
    ///TODO: implement
}
 
/**
 * Compute n-th number of fibonacci series.
 * @param n
 * @return n-th number
 *
 * Hint: use trivial recursive variant
 */
constexpr int fib(int n) {
    ///TODO: implement
}
 
/**
 * Compute n-th number of fibonacci series with using linear number of recursive calls.
 * @param n
 * @return n-th number
 *
 * Hint 1: you need more than 1 constexpr function
 * Hint 2: int has 4 bytes, two numbers might be returned from a function if they are returned as a 8 bytes long numeric type
 */
constexpr int fibSmart(int n) {
    ///TODO: implement
}
 
/**
 * Compute sum of numerical series
 *
 * @param offset The begin of the series.
 * @param add Difference between two numbers in the series.
 * @param limit How many numbers of the series will be used.
 *
 * Example: seriesSum(1, 2, 5) == 1 + 3 + 5 + 7 + 9 == 25
 */
constexpr int seriesSum(int offset, int add, int limit) {
    ///TODO: implement
}
 
int main() {
    static_assert(fact(5) == 120, "Error - invalid factorial number.");
    static_assert(seriesSum(1, 7, 13) == 559, "Error - invalid series sum.");
    static_assert(fib(11) == 89, "Error - invalid fibonacci number.");
    static_assert(fibSmart(19) == 4181, "Error - invalid fibonacci number.");
 
    return 0;
}

Druhý úkol - range-for cyklus

Nejen v jazyce C++ se často iteruje přes různé kontejnery. Protože je v jazyce C++ procházení kontejnerů realizováno pomocí iterátorů, bývá zápis pomocí klasického for cyklu zdlouhavý, přičemž tento zápis obsahuje mnohdy duplicitní sekvence kódu. A například typy iterátorů si umí překladač sám odvodit. (Nejen) z těchto důvodů byl zaveden další druh cyklu, varianta na cyklus foreach známý z dalších programovacích jazyků.

Oprava poskytnutého kódu

#include <iostream>
 
int main() {
    for ( auto i : { 1, 2, 3, 5, 9, 10, -6 } )
        std::cout << i << std::endl;
}

Prvním podúkolem je analýza a menší úprava krátkého kódu. Výše uvedený kód vypíše postupně uvedená čísla, každé na samostatný řádek. Ovšem kdybychom chtěli vypsat nikoliv jedno číslo, ale kupříkladu dvojici čísel, dostaneme se do situace, že kód nelze přeložit.

#include <iostream>
 
int main() {
    for ( auto i : { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } } )
        std::cout << i << std::endl;
}

Upravte výše uvedený kód za použití C++11 vymožeností takovým způsobem, aby zápis zůstal nadále elegantní a snadno použitelný. Hint: using, uniformní inicializace.

Problém spočívá v tom, že překladač sice ví, že vnější složené závorky ohraničují pole, už si ale neví rady s typem složených závorek u dvojic čísel.
Samotný zápis
auto x = { 1, 2, 3 };

způsobí, že proměnná x bude typu std::initializer_list< int > – a to i v případě, že nebude mít v souboru přítomné #include <initializer_list>.

Implementace tříd Range a XRange

Dalším podúkolem je implementace tříd Range a XRange, které se mají tvářit jako pole čísel v zadaném rozsahu, přes které bude možné iterovat v range-for cyklu. Rozdíl mezi třídou Range a XRange je ve způsobu alokace pole. Třída Range alokuje pole, které vyplní požadovanými hodnotami; následně se přes toto pole iteruje (hint: použijte std::unique_ptr<T[]>). Třída XRange je šetrnější k paměti, protože nic nealokuje, vytváří pouze chytrý iterátor, který vytváří iluzi pole.

Je proto požadováno následující rozhraní. Typ ??? záleží zcela na vaší implementaci, jediné, co musí splňovat, je to, že tento typ musí jít použít pro iterování v range-for cyklu.

class _Range {
public:
     _Range(int length); // start at 0, increment = 1
     _Range(int start, int length); // increment = 1
     _Range(int start, int length, int increment);
 
     ??? begin() const;
     ??? end() const;
};

Příklad použití

for ( auto i : XRange( 10 ) )
    std::cout << i << ", "; // 0, 1, 2, ... 10, 

Třetí úkol - lambda funkce

Anonymní funkce je populární koncept napříč jazyky, který se snadno používá, snadno píše a ke všemu je z funkcionálního paradigmatu.

Staré C++ sice také mohlo posílat jako věci na zavolání nejen funkce a metody, bylo možné posílat i celé objekty a použít přitom operátor volání. Pokud jste si ale zkoušeli psát a následně používat tyto předchůdce lambda funkcí – tzv. funktory – zjistili jste nejspíš, že pro samou otravu z důvodu psaní zbytečného kódu a vymýšlení názvů pro velmi podobné třídy jste si nemohli plně vychutnat pocit uspokojení z dobře napsaného funkcionálního programu v C++.

V C++11 tento problém mizí, je již možné používat lambda funkce, které prostě napíšete v místě použití, čímž šetříte nutné množství napsaného kódu a svoje nervy. Je ale nutné podotknout, že oproti jazykům jako C# nebo JavaScript je v C++11 stále nutné napsat stále příliš mnoho. Ovšem není třeba zoufat, C++14 některé z těchto nešvarů odstraňuje.

Tvorba "SQL" dotazů

V již připraveném archivu máte k dispozici několik manipulátorů s kontejnery. Použijte je k tomu, abyste získali kolekce dle zadaných restrikcí. Výsledné kolekce lze procházet pomocí range-for cyklu.

Archiv obsahuje soubory main.cpp, do kterého budete psát lambda funkce, linq.h, který obsahuje definice metod, které budete používat, a soubor person.h, který obsahuje definice některých užitečných tříd a funkcí. V posledním souboru – internal.h – najdete poněkud komplikované definice funkce zip a dalších, které sice použijete, nicméně nikdo po vás nebude požadovat, abyste kódu byť i jenom trochu rozuměli. Můžete do něho však nakouknout, abyste měli představu, jak také může vypadat C++11 zdrojový kód.

Restrikce

  • Vypište pouze lidi, kteří mají sudou délku příjmení.
  • Vytvořte anonymní statistiku lidí, kterou následně vypíšete.
  • Vypište pouze lidi, jejichž křestní jméno je kratší, než jméno člověka v seznamu před ním.
  • Vytvořte a následně vypište pole řetězců, přičemž každý řetězec bude ve formátu věk;jméno;příjmení, ale pouze u lidí, kterým bylo 18 let.

Implementace modifikátoru

Implementujte do třídy LINQ novou metodu Self take( int ), která zkrátí kontejner na požadovanou velikost. Inspirujte se metodou where.

Bonusový úkol

Vzorové řešení

Odkaz na řešení - jeden archiv - „vyplněné“ podklady.

QR Code
QR Code public:pb161:fall14_cviko12 (generated for current page)