Hlavní navigace

Reportáž z C++Now 2013

27. 6. 2013
Doba čtení: 17 minut

Sdílet

Již po sedmé se v Aspenu ve státě Colorado konalo týdenní setkání vývojářů a příznivců jazyka C++ pod názvem C++Now 2013. Následující reportáž podrobně popisuje uběhlý týden, který se celý nesl ve znamení zajímavých a někdy i doslova překvapivých přednášek od neméně zajímavých autorů důležitých projektů.

Konference C++Now je jedna z mála vývojářských konferencí věnujících se jazyku C++. Předloni se ještě jmenovala BoostCon a soustředila se hlavně na Boost, ale v roce 2012 byla kvůli širšímu záběru i mimo svět Boostu přejmenována. Samotná konference si přes svůj mezinárodní a prémiový obsah stále zachovává komorní charakter a ze cca stovky účastníků skoro polovina buď přednáší, nebo jiným způsobem participuje na organizaci konference. Není tedy problém si odchytit kohokoliv a u piva s ním rozebrat vše, co vás zajímá. Ostatně hned na welcome session jsme k tomu byli vyzváni a ze zkušenosti můžeme říci, že pokud se zrovna nepřednášelo, tak se všude tvořily hloučky, ze kterých se ozývala ta správná klíčová slova.

Letos byl záběr konference opravdu široký a nabitý, jak se ostatně sami můžete přesvědčit z programu. Začínalo se každý den v osm hodin ráno a po večeři byla možnost účastnit se různých socializačních akcí, jako například grill the committee. Boost a jeho knihovny samozřejmě zůstaly podstatnou částí, ale prostor dostalo i spoustu jiných Open Source knihoven, jako jsou Qt, POCO, … Některé přednášky se věnovaly novému standardu C++11 a velmi zajímavé přednášky byly o pokusných implementacích nových vlastností jazyka, jako jsou Concepts Lite či přetížení operátoru tečka.

Díky našemu zaměstnavateli jsme měli možnost tuto konferenci navštívit. To zajímavé, co jsme během konference slyšeli a viděli, si teď, popsané den po dni, můžete přečíst. Na samotné přednášky se můžete podívat formou videozáznamů, a pokud zatím nejsou k dispozici, tak alespoň formou slajdů.

Aspen Center for Physics

Den první, zahřívací

Library in a Week. Z nic neříkajícího názvu přednášky na začátku každého dne konference se vyklubal zajímavý nápad, k jehož realizaci byli přizváni všichni účastníci konference. Vývojáři používající jazyk Perl mají k dispozici úžasnou kuchařku, která obsahuje rady, návody a hlavně příklady pro drtivou většinu věcí, se kterou se nejen začínající vývojář může setkat. Pro jazyk C++ nic tak komplexního, ale hlavně jednoduchého a přímočarého, neexistuje. V rámci těchto session tedy měly vzniknout první části této kuchařky, která na příkladech ukáže, jak správně řešit spoustu problémů za pomoci C++11 a Boostu. Vývoj této kuchařky můžete nejen sledovat na GitHubu, ale i třeba sami přispět.

Nový standard C++11 je tu s námi již více jak rok a půl a kompilátory GCCClang jsou čerstvě C++11 feature complete, takže na konferenci nemohla chybět přednáška o novinkách v tomto standardu. Protože se nový standard připravoval přes 10 let a je tedy dosti obsáhlý, byly přednášce vyhrazeny 3 hodiny. Leor Zolman se v ní pokusil projít opravdu vše, od drobných vylepšení jazyka jako auto, range based for loop nebo nullptr až po složitější věci jako rvalue references/move semantics, variadic templates nebo lambda funkce.

Část přednášky byla věnovaná i novým věcem ve standardní knihovně, z nichž je velká část převzatá z Boostu, nebo je umožnily právě nové vlastnosti jazyka C++11, jako například funkce emplace_* v C++11 kontejnerech. Bohužel úplně vše se za tak krátký čas stihnout nedá, a tak se na některé věci přece jen nedostalo. Týkalo se to především podpory vláken v C++11, tu však naštěstí pokryl Rob Stewart ve své přednášce o práci s vlákny a Tony Van Eerd, který popsal nově definovaný memory model v jazyce C++ a std::atomic.

Tony pak pokračoval první teoretickou přednáškou, kdy se pokusil nastínit problémy, které mohou nastat při pokusu o implementaci std::future a std::promise bez kopírování. Jeho přednáška byla krásným cvičením na hledání potenciálních problémů souběhu, kdy Tony začal s velice jednoduchým přímočarým řešením a společně s námi hledal problémy a řešil je. Jím použité slajdy, obzvláště pak poslední část, nepotřebují slovní doprovod, a tak pokud se chcete procvičit, nelze než doporučit jejich prostudování.

Večer nás čekalo první grilování, jen se místo masa a hamburgerů mělo grilovat cirka 10 zástupců C++ committee. Na pódiu se sešla pestrá skupinka lidí různého věku a pohlaví, na kterých byla na první pohled poznat tréma. Ostatně těsně před začátkem prořídlé hlediště rozesmála věta „Tony, keep going“, která zazněla z pódia na adresu jednoho příchozího, kterému se na gril asi moc nechtělo.

Po představení přítomných členů committee a dotazu do publika na otázky, následovala dlouhá pomlka v závěsu s druhým vtipem večera, kdy jeden z přítomných položil dotaz „Mě by zajímal návrh číslo N1234?“ a z committee se ozvalo „Aaa N1234!“. Smích prolomil ledy a následovala diskuze o tom proč tak dlouho trvalo vydání C++11 a jak to bude s C++14. Committee se prý do budoucna bude snažit vydávat standardy cirka v tříleté periodě a do standardu se dostanou pouze aktuálně zpracované a schválené návrhy. Návrhy, které committee nestihla schválit, budou na rozdíl od C++11 odloženy na příští vydání. Diskuze pokračovala směrem k tomu, co nás čeká v novém standardu. Docela pěkné shrnutí lze nalézt na webu podobné evropské konference Meeting C++. Konec večera, a to poměrně dlouhý, byl pak věnován konceptům. Zatím to s nimi pořád nevypadá moc dobře a do C++14 se pravděpodobně místo nich dostanou Concepts Lite.

Den druhý, grilovací

Po každodenních LIAW nás upoutal Chandler Carruth. Chandler se mimo jiné věnuje práci na optimalizátoru Clang kompilátoru a svoji key note věnoval tomu, co takový optimalizátor dokáže a co mu ztěžuje práci. Bylo to ohromně zajímavé a poutavé povídání, které se dá vystihnout větou „Use value semantics and trust the compiler – you don't known how smart it is.“

Use value semantics and trust the compiler – you don't known how smart it is

Tedy, že v době move semantics a copy elision nejenže nemá smysl předávat návratovou hodnotu referencí v parametrech, či vracet pointery na dynamicky alokovaná data, ale je i lepší předávat funkcím jejich parametry hodnotou. To dává optimalizátoru garanci neměnnosti parametrů, a tedy mnohem více možností, jak danou funkci optimalizovat. Tato key note je rozhodně must see a neměla by nikomu, koho C++ zajímá, uniknout.

S optimalizacemi jsme tento den ještě neskončili, protože hned na Chandlerův key note navazoval Edouard Alligand, se svojí přednáškou Scaling with C++11. Edouard popsal způsob, jakým postupovali při psaní NoSQL databáze quasardb, která prý zvládne až jeden milión requestů za sekundu.

Jeho receptem na takto výkonnou databázi je důsledně navrhovat aplikaci jako škálovatelnou, a to někdy i na úkor čitelnosti a rozšiřitelnosti. Proto Edouard hned na začátku zdůraznil, že v drtivé většině případů není třeba takto optimalizovat až na dřeň, ale spíše udržovat aplikaci čitelnou a udržovatelnou. Při implementaci se snažili vyhnout jakýmkoliv sdíleným stavům a důsledně rozdělit aplikaci do modulů, které si mezi sebou předávají práci kopírováním. Při odstraňování sdílených stavů šli až tak daleko, že používají raw pointery ( v každém std::shared_ptr je sdílené počítadlo) a mají vlastní alokátor, který přepoužívá alokovanou paměť, ale občas i někde něco upustí. Což je daň, kterou se jim vyplatí zaplatit.

Dalším přednášejícím byl John Bandela, jehož hlavním oborem překvapivě není programování, ale neurochirurgie. Programování je pouze jeho koníček. Johna zaujal nový standard C++11 a chtěl si nové vlastnosti jazyka vyzkoušet. Narazil však na neúplnost implementace v jím preferovaném kompilátoru MS Visual C++, a tak si některé věci zkoušel v GCC. Pak ale nebyl schopen kombinovat zkompilované objektové soubory z MS Visual C++ a GCC dohromady, a proto začal vytvářet knihovnu, kterou popisoval ve své přednášce, jež řeší problém neexistence binárně kompatibilního rozhraní mezi různými C++ kompilátory.

Celá myšlenka je ve své podstatě jednoduchá, vlastní implementace je pak už složitější. Na low level vrstvě se používá vlastní vtable, tedy struktura s odkazem na funkce. Data se předávají pomocí klasického rozhraní čistého C. Nad tím je high level vrstva, která už umí pracovat se std::vector, std::string, std::pair a výjimkami. Ta na jedné straně podporované objekty rozloží na C typy, které se přenesou přes low level interface a na druhé straně se opět složí v C++ objekty. Knihovna ale hlavně obsahuje spoustu syntactic sugar, takže se to celé relativně jednoduše používá.

Před večerním grilováním ještě Michael Wong stihl představit do standardu nově navrhovanou transakční paměť. Věnoval se hlavně rozdílům v chování různých typů transakcí a jejich výjimkové bezpečnosti. Nakonec přednášky samozřejmě padla otázka, kdy se může tato velice zajímavá vlastnost objevit ve standardu. Podle Michaela je možné, že se to stihne ještě do C++14.

Na dnešní večer bylo plánované druhé grilování, na rozdíl od včerejšího večera se grilovaly hamburgery, klobásky a maso. Samozřejmě, že nám přálo počasí a začátek večera svlažil docela čerstvý déšť. U grilů postávali pod deštníky členové Boost committee a grilovali, až se z nich kouřilo. My jsme utvořili českou skupinku o šesti členech posbíraných z různých částí světa a zaujali strategickou pozici mezi grily a lavory, ve kterých bylo ledem chlazené limonády a pivo. Během večera jsme probrali spoustu věcí, ale hlavně ocenili podávané pivo – na tamější poměry opravdu slušné chutě.

Den třetí, tak trochu odpočinkový

Třetí den začal poněkud netradičně a také předznamenal jeho odlehčenou náplň. Stanley Lippman, bývalý vývojář a architekt cfront, pustil na začátku svého key note slideshow plnou myší. Těch živých a chlupatých, co mají rády sýr.

Stanley Lippman a jeho myši

Po key note pokračoval třetí den pro mě osobně nejzajímavější přednáškou celé konference nazvanou Inside Spirit X3: Redesigning Boost.Spirit for C++11. Knihovna Boost.Spirit umožňuje deklarativním způsobem implementovat LL parsery pomocí pravidel EBNF gramatik. Alespoň tak zní teorie; praxe naráží na dva problémy, které si s sebou knihovna nese od své první verze a jsou poplatné použitým implementačním prostředkům.

Boost.Spirit masivně využívá metaprogramování, což znamená, že i malé parsery se kompilují někdy až neúnosně dlouho. Navíc pokud při kompilaci dojde k chybě, tak někdy bývá dost obtížné chybu odhalit. Ostatně zdrojový kód knihovny je protkán komentáři ve stylu: „Pokud vám kompilátor hlásí chybu na této řádce, pak jste asi udělal takovouto chybu.“ Joel de Guzman se proto pustil do implementace zbrusu nové verze, která se pokusí tyto problémy vyřešit, nebo alespoň zmenšit.

Verze X3 je založena na nových vlastnostech jazyka C++11, a to hlavně na typové inferenci – tedy využívání klíčových slov auto a decltype. Joel vytvořil proof of concept, který se vejde na pár řádek a velice hezky ilustruje použité základní myšlenky. A co víc, vlastní knihovna je zatím, až na syntaktický šum, který je typický pro přenositelné a obecné knihovny, pořád stejně jednoduchá.

Každé pravidlo gramatiky je nyní definováno pomocí dvou tříd, což umožnilo se zbavit virtuálních členských funkcí. První třída reprezentuje pravou stranu pravidla gramatiky (v příkladu: proměnná x) a druhá třída je prostředníkem, který tomuto pravidlu přiřazuje definici (v příkladu: přiřazení do proměnné x).

namespace grammer {

// Příklad zápisu jednoduché gramatiky:
// X -> 'a' X | 'x'

rule<class x> const x;

const auto ax = char_('a') >> x;
const auto start = (x = char_('x')) | ax;

} // namespace grammer

int main() { return parse(grammer::start, "aaax");}

Verze X3 zdaleka není hotová a probíhá na ní aktivní vývoj. Vývojáři zatím dokončili podporu pro základní znakové a číselné parsery, což jim umožnilo si otestovat použité koncepty. Prozatím tedy knihovna nenabízí žádné možnosti pro navěšení sémantických akcí, není k dispozici lexer nebo podpora pro generátory, což jsou úkoly, které vývojáře zatím čekají.

Ze středečního dne stojí za zmínku ještě další meta přednáška, na které Joel Falcou mluvil o knihovně Boost.Dispatch, která se snaží o začlenění do Boostu. Joel řeší problémy jak optimalizovat numerické výpočty pro různorodé platformy – a na to přesně tato knihovna cílí. Knihovna nabízí více jak 500 funkcí detailně optimalizovaných pro více jak 10 platforem.

Udržovat takovýto kód není jednoduché, a tak se vývojáři snaží ulehčit si práci hierarchickou definicí tagů pro podporované platformy a optimalizované funkce specializují za pomoci techniky tag dispatching. Tedy stejně, jako jsou například ve standardní knihovně implementovány a využívány tagy pro iterátory.

Techniku demonstruje následující příklad, kde pro typy necelých čísel bude volána funkce f vracející nulu, pro celočíselné typy se znamínkem pak funkce f vracející jedničku a pro celočíselné typy bez znaménka verze vracející dvojku.

template <class T>
int f(T t) {
    return f(t, typename meta::hierarchy_of<T>::type());
}

template <class T>
int f(T, scalar_<floating_<T>> const &) {
    return 0;
}

template <class T>
int f(T, scalar_<signed_<T>> const &) {
    return 1;
}

template <class T>
int f(T, scalar_<unsigned_<T>> const &) {
    return 2;
}

Den čtvrtý, nejhutnější

Čtvrtek začal poněkud netradičně sice opět LIAW, ale místo key note následovaly rovnou první přednášky.

Andrew Sutton za účasti početného publika a přítomných členů C++ committee představil Concepts Lite a sklidil zasloužený potlesk, ale i bouřlivé emoce. Původně měla být součástí C++11 plná implementace konceptů, ale nakonec se do standardu nedostala a růžově to s ní nevypadá ani pro C++14. Proto bokem vznikají Concepts Lite, které mají řešit pouze určitou část problémů, a to omezování instanciací šablon pouze na typy splňující daný koncept. Autoři se, pokud možno, snaží držet jak myšlenky konceptů, tak také jejich syntaxe, i když ji doplňují a zjednodušují, což se evidentně ne úplně líbí všem členům committee. Počítají s tím, že jakmile budou dořešené problémy s plnými koncepty, tak na ně bude možné z Concepts Lite díky stejné syntaxi bez problémů přejít.

Navržená zjednodušená syntaxe má rozhodně něco do sebe. Ostatně porovnejte si sami šablonový zápis a nově navrhovaný a představte si, že požadavků je podstatně více.

template <typename T>
concept bool Equality_comparable() {
    return has_eq<T>::value
           && is_convertible<eq_type<T>, bool>::value
           && has_ne<T>::value
           && is_convertible<ne_type<T>, bool>::value;
}

template <typename T>
concept bool Equality_comparable() {
    return requires (T a, T b) {
               {a == b} -> bool;
               {a != b} -> bool;
    };
}

Korunu zjednodušování nasadil Andrew ve chvíli, kdy pro šablony s jedním
parametrem předvedl tento zápis:

void sort(Sortable_container_concept &cont);

Zde Sortable_container_concept není typ, ale koncept, který tento typ zastupuje. Concepts Lite rozhodně nejsou teoretickou záležitostí a zatím to vypadá, že se v nějaké formě dostanou do C++14. Andrew na přednášce ukázal funkční implementaci v GCC, kterou si sami můžete vyzkoušet.

Ten, kdo si dal větší snídani, a nebo prostě neměl hlad, mohl dnes místo oběda navštívit bonus session. Na jedné z nich Boris Schäling představil knihovnu Boost.Graph. Ukázal, jak s její pomocí vytvořit graf a velice rychle prolétl seznam všech možných i nemožných grafových algoritmů, které jsou v knihovně implementovány.

Zatímco Boris vyprávěl o grafech, Alisdair Meredith si se svým představením alokátorů v C++11 zadělal na výhru v kategorii best tutorial. To, jak jsou alokátory v C++03 navrženy, přináší spoustu problémů a víceméně jsou přinejlepším obtížně použitelné. Už jenom to, že typ alokátoru je součástí typu alokovaného objektu, velice komplikuje zcela běžnou práci se stejnými objekty alokovanými různými alokátory. Alokátor navíc nesmí obsahovat stav a docela úspěšně se brání mnoha pokusům tento stav do něj propašovat. Bez stavu se pak těžko implementují prakticky jakékoliv uživatelské alokátory, neboť jakýkoliv jiný způsob alokace než malloc tento stav potřebuje. V C++11 se toto změnilo a nelze než doporučit Alisdairovu přednášku, která změny popisuje. Zatím pouze ve formě slajdů.

Knihovna Boost.Fusion nabízí heterogenní kontejnery. Její použití však vyžaduje pochopení alespoň základů šablonového metaprogramování a s tím občas i přehrabování se ve velice odpudivých chybových hlášeních kompilátoru. To může být pro mnoho programátorů problém. Přesto by jim v některých případech mohla usnadnit život. Michael Caisse proto ukázal, jak s její pomocí řešit reálné problémy a nenabít si přitom ústa.

Sebastian Redl měl přednášku na téma spíše teoretické. Zkusil se zamyslet nad tím, jaké výhody a nevýhody by měla možnost přetížení operátoru tečka. Aby si tuto vlastnost mohl vyzkoušet, předvedl i experimentální implementaci pro Clang. V jeho přístupu však nešlo o pouhý ekvivalent přetížení operátoru šipka. Podstatný rozdíl je ten, že název member proměnné či member funkce by byl vlastně argument přetíženého operátoru. Použití potom vypadá nějak takto:

class property {
public:
    template <__tstring name>
    string operator.() { return (*this)[name];}

    string operator[](const char *name) { return property_map[name];}
};

class data_proxy {
public:
    template <__tstring name>
    auto operator.() -> decltype(data.*name) { return data.*name;}

private:
    data_type data;
};

V rámci přednášky pak popsal spoustu problémů, se kterými se při implementaci
setkal a vždy ukázal výhody i nevýhody jednotlivých řešení (například jak řešit
konflikt s názvem member třídy, který už existuje). Vzhledem k množství
problémů je toto věc, která se asi v nejbližší době v žádné formě do
standardu nedostane. I když na spoustu věcí (přístup k JSONu, různé proxy) by
to byla vlastnost poměrně šikovná.

Čtvrteční přednášky uzavřel Zoltan Porkolab, který představil debugovací a profilovací nástroj pro metaprogramy. V silně generických knihovnách jako jsou Boost.MPL, Boost.Fusion či Boost.Spirit se instanciuje obrovské množství šablon a pokud se vám podaří v takovémto kódu udělat jakoukoliv chybu, tak je velice obtížné ji najít pomocí prostého chybového hlášení kompilátoru, které obsahuje úplnou specifikaci typu. Tato specifikace totiž může zabrat i několik obrazovek. Najít mezi vyjmenovanými typy skutečné hlášení chyby může být opravdu nadlidský úkol. Poslední dobou se sice kompilátory snaží tento problém řešit, ale zdaleka se nedá prohlásit, že hlášení chyb v generickém kódu jsou snadno pochopitelná.

Srovnání debugovacích nástrojů

Templigth, jak se nástroj jmenuje, je ve své druhé verzi založen na Clang 3.2 a skládá se z patche na kompilátor generující xml soubor popisující běh metaprogramu a vizualizačních nástrojů tohoto souboru. Zatím je k dispozici nástroj pro zobrazení grafu instanciací, který také umožňuje krokovat metaprogram a vkládat do něj breakpointy. Druhý nástroj, pak lze použít pro ladění doby běhu metaprogramu, tedy dobu kompilace jednotlivých instanciací. Oba vizualizační nástroje jsou zatím v plenkách a vývojáři, kteří chtějí pomoci, jsou samozřejmě vítáni.

Aspen Center for Physics: Paepcke Auditorium

Asi nejzajímavější, ale i nejnáročnější den zakončila po osmé hodině večerní diskuze na možná lehčí téma, a to budování C++ komunity.

Den pátý, poslední

Na rozdíl od předchozího dne, začal poslední den konference odlehčeným rozvrhem. Dopoledne nás čekaly poslední přednášky a na odpoledne byla naplánována diskuze s Boost committee a organizátory konference na téma budoucnost Boostu a jazyka C++.

Člověk by si řekl, že ho po čtyřech dnech už nic nepřekvapí, ale opak byl pravdou a došlo na příslovečné to nejlepší na konec. Julian Storer si připravil představení úžasné hračky v Projucer IDE. Samotné IDE je postaveno nad multiplatformní knihovnou pro GUI aplikace JUCE a Julian do něj implementoval za pomocí LLVM JIT engine editaci aplikace v runtime režimu.

Přednáška začala relativně normálně, Julian ukázal docela běžné IDE a vyrobil v něm za pomocí JUCE komponent malou aplikaci s tlačítkem a následně ji také zkompiloval a pustil. Všechny posluchače si pak Julian získal ve chvíli, kdy myší roztáhl zmíněné tlačítko a ve zdrojovém kódu se začaly měnit konstanty popisující jeho velikost. No dobrá, řeknete si, nějak nahákoval editaci konstant u několika komponent. Ale veškeré pochyby nad tím, co dokáže, se rozplynuly, když do běžící aplikace přidal červený čtvereček editací zdrojáků.

Celá magie, která je za touto vlastností schovaná, se provádí za pomoci editace stromové AST reprezentace zdrojového kódu, která je runtime kompilovaná pomocí LLVM JIT kompilátoru a automaticky natažená do běžící aplikace. Z tohoto řešení vyplývají samozřejmě různá omezení. Nelze třeba měnit hlavičky funkcí, odebírat je, atd.

Poslední část přednášky byla věnována rychlosti kompilace, kterou Julian demonstroval na zkompilování Projucer IDE za cca. 10 s a jeho spuštění právě v rámci Projucer IDE. Celá přednáška byla opravdu překvapující – a ostatně nejlepší je rovnou se podívat na screencast přímo od autora a udělat si vlastní názor. Většina účastníků konference tuto přednášku při závěrečném hlasování označila za most inspiring.

Poté následovala přednáška Louise Dionne, který se zabývá v rámci svého postgraduálního studia vývojem intrusivní knihovny na detekci deadlocků způsobených zamykáním zámků v chybném pořadí. Knihovna je v začátcích a vývojáři dobrovolníci jsou vítáni.

Celou konferenci zakončila diskuze o tom, co přinese příští rok Boostu a jazyku C++. Na začátku této diskuze byla vyhodnocena anketa, kterou bezkonkurenčně vyhrál Andrew Sutton s přednáškou „Concepts Lite: Constraining Templates with Predicates“, a to jak v kategorii best presentation, tak i v kategorii most engaging. Při volbě v kategorii best tutorial došlo k remíze a na prvním místě se umístil Alisdair Meredith s přednáškou „Allocators in C++11“ a Rob Stewart s přednáškou „Survey of Multi-Threaded Programming Support in C++11 and Boost“. No a nejužitečnější přednáškou byla zvolena „Solving World Problems With Fusion“ od Michaela Caisse.

CS24_early

V diskuzi zaznělo, že pro knihovny Boost mají vývojáři připraveny konverzní skripty na přechod na Git, nebo že se stále budou udržovat a vyvíjet všechny části Boostu, které se dostaly do nového standardu C++11. Například protože jejich funkcionalita často nabízí některé další možnosti, které se zatím do standardu nedostaly a nebo nedostanou.

Naneštěstí jedna z nejzajímavějších otázek – co ještě chybí v Boostu – zůstala skoro nezodpovězena, neboť někteří členové Boost committee spěchali na letiště. Ale i tak, byly zmíněny některé zajímavé návrhy: SQL, UTF8, HTTP, … Na nás letadlo čekalo až příští den, takže jsme se mohli v klidu připravit na zpátečních skoro 24 hodin cesty domů.

Byl pro vás článek přínosný?