V srpnu tohoto roku byl schválen nový standard jazyka C++, označovaný jako C++14. Pojďme se společně podívat, co je v něm nového oproti C++11.
Poznámka na začátek: Tento příspěvek de facto navazuje na můj předchozí příspěvek o novinkách v C++11. Budu se tedy zabývat pouze tím, co je v C++14 nového oproti C++11 a předpokládám, že C++11 znáte.
Kde jsme a kam směřujeme
Předchozím standardem je C++11, které bylo schváleno v srpnu roku 2011 a nahradilo C++98. Tento standard je nahrazen právě schváleným standardem C++14, který byl taktéž někdy označován jako C++1y. Poslední revizi draftu lze stáhnout zde. Oproti dá se říct revolučnímu C++11, které přineslo řadu novinek, přináší C++14 spíše drobnější vylepšení a řadu oprav. Dalším větším standardem (z pohledu novinek) bude C++1z, které je plánováno na rok 2017.
Co je nového v C++14
Pojďme se tedy podívat, co je v C++14 nového.
Rozšířená dedukce návratového typu funkcí
C++11 umožňovalo automatickou dedukci návratového typu u jednoduchých lambda funkcí:
// C++11 [](int x, int y) -> int { return x + y; } // Explicitní specifikace návratového typu. [](int x, int y) { return x + y; } // Automatická dedukce návratového typu.
C++14 jde ještě dál a rozšiřuje možnost dedukce návratového typu na (1) složitější lambda funkce obsahující více příkazů return
, (2) klasické (ne-lambda) funkce a dokonce i na (3) rekurzivní funkce:
// C++14 auto factorial(int n) { if (n == 0) { return 1; } return n * factorial(n - 1); }
U rekurzivních funkcí je však podmínka, že před rekurzivním voláním se musí nacházet příkaz return
.
Generické lambda funkce
V C++11 je u lambda funkcí potřeba vždy explicitně deklarovat konkrétní typ parametrů:
// C++11 auto addInt = [](int x, int y) { return x + y; } auto addDouble = [](double x, double y) { return x + y; } ...
C++14 od tohoto požadavku upouští a umožňuje tak psát tzv. generické lambda funkce:
// C++14 auto add = [](auto x, auto y) { return x + y; }
To se hodí, protože šablonové lambda funkce nejsou podporovány.
Zobecněné lambda captures
V C++11 lze specifikovat, zda se proměnné z vnějšího rozsahu (angl. outer scope) zachytí (angl. capture) hodnotou ([=var]
) či referencí ([&var]
):
// C++11 int total = 0; std::for_each(begin(container), end(container), [&total](int x) { // ^^^^^^ // Výše je potřeba použít '&', jinak by se změny neprojevily do volající funkce. total += x; });
Nelze však jednoduše specifikovat zachycení proměnných, jejichž typ umožňuje pouze přesun (angl. move). Jedná se např. o proměnné typu std::unique_ptr
. C++14 toto umožňuje tak, že zavádí možnost definování lokálních proměnných v tzv. capture části lambda funkcí, které lze inicializovat libovolným výrazem. Toho lze využít např. právě pro zachycení přesunem (angl. capture by move):
std::unique_ptr<T> ptr(new T()); auto lambda = [value = std::move(ptr)] { return *value; };
Snížení omezení kladených na constexpr
C++11 zavedlo nové klíčové slovo constexpr
, pomocí kterého lze označit konstantní výrazy či funkce vyčíslitelné za překladu. Na takové funkce ale C++11 klade řadu omezení. C++14 tato omezení snižuje, a to tak, že umožňuje, aby se v constexpr
funkcích vyskytovaly
- lokální proměnné,
- příkazy
if
,switch
,for
,while
ado-while
, - výrazy, které modifikují objekty, jejichž doba životnosti započala uvnitř
constexpr
funkce.
Nyní lze tedy napsat např. takovouto constexpr
funkci (převzato s jistými modifikacemi odtud):
// C++14 constexpr int my_strcmp(const char *str1, const char *str2) { int i = 0; while (str1[i] && str2[i] && str1[i] == str2[i]) { i++; } if (str1[i] == str2[i]) return 0; if (str1[i] < str2[i]) return -1; return 1; }
Šablonové proměnné
V C++11 lze definovat šablony funkcí, tříd a typových aliasů. C++14 zavádí možnost vytváření šablon proměnných. Ukázka (převzato odtud):
// C++14 template<typename T> constexpr T pi = T(3.14159265358979323846); // Přesnost je dána šablonovým parametrem. // Použití: template<typename T> T area_of_circle_with_radius(T r) { return pi<T> * r * r; }
Binární literály
Od C++14 jsou k dispozici celočíselné literály zapsané v binární podobě. Ukázka:
// C++14 auto a = 0b101010; // 42
Dříve šlo celočíselné literály zapisovat pouze oktalově (052
), decimálně (42
) či hexadecimálně (0x2a
).
Oddělovač skupin číslic
V C++14 lze použít jednoduchou uvozovku ('
) nejen pro zápis znakového literálu ('a'
), ale i jako oddělovač skupin číslic číselných literálech. Příklad:
// C++14 auto million = 1'000'000;
Na funkcionalitu to nemá žádný vliv. Jedná se pouze o zčitelnění zápisu. Sranda je, když si otevřete zdroják obsahující 1'000
v editoru, který nepodporuje C++14; obarví se vám vše za první uvozovkou :).
Tento zápis lze použít i např. u binárních literálů pro oddělení jednotlivých nibblů:
// C++14 0b1000'0001'1000'0000
Atribut [[deprecated]]
Většina překladačů poskytuje možnost označit proměnnou, funkci atd. jako zastaralou (angl. deprecated). Např. __attribute__((deprecated)) void f();
pro GCC. Je ji stále možno použít, ale při jejím použití vypíše překladač varování. C++14 zavádí přenositelnou variantu: atribut [[deprecated]]
. Příklad:
// C++14 [[deprecated]] void f();
Ukázka varování při zavolání dané funkce:
file.cpp:6:2: warning: 'f' is deprecated [-Wdeprecated-declarations] f(); ^ file.cpp:3:6: note: 'f' has been explicitly marked deprecated here void f(); ^
Volitelně lze specifikovat vlastní zprávu, která se zobrazí uživateli, který tuto funkci použije:
// C++14 [[deprecated("Superseded by g().")]] void f();
Pokud by vás zajímaly další atributy, které lze v přenositelném C++ kódu použít, mrkněte zde.
decltype(auto)
C++14 zavádí možnost použít decltype(auto)
místo decltype(expr)
pro zkrácení zápisu. Příklad (převzat odtud):
// C++11 decltype(longAndComplexInitializingExpression) var = longAndComplexInitializingExpression; // C++14 decltype(auto) var = longAndComplexInitializingExpression;
Možná se ptáte, proč nepoužít pouze auto
. Důvod je ten, že auto
a decltype()
se v některých případech chovají mírně odlišeně a s použitím pouze auto
by ona proměnná mohla mít jiný, než požadovaný typ (detaily).
Standardní uživatelsky definované literály
C++11 zavedlo možnost vytváření uživatelsky definovaných literálů. C++14 zavádí následující nové standardní literály:
"s"
pro vytváření řetězcových literálů odvozených zstd::basic_string
,"if"
,"i"
a"il"
pro vytváření komplexních čísel"h"
,"min"
,"s"
,"ms"
,"us"
a"ns"
pro vytváření literálů typustd::chrono::duration
.
Ukázka:
// C++14 auto name = "Petr Zemek"s; auto runtime = 30s;
Odkazy: 1.
Přístup k členům ntic pomocí typu
U ntic, zavedených v C++11, se k jednotlivým prvkům dá přistoupit přes index. C++14 umožňuje taktéž indexaci pomocí typu. Ukázka:
std::tuple<std::string, int> t("Martin", 25); auto i = get<1>(t); // C++11/14, i == 25 auto j = get<int>(t); // C++14, j == 25
Samozřejmě, pokud by ntice obsahovala více členů stejného typu, tak se pro přístup k nim musí použít index.
Odkazy: 1.
Drobnosti ve standardní knihovně
Do standardní knihovny přibyly následující záležitosti:
std::make_unique()
jako analogiestd::make_shared()
prostd::unique_ptr
(toto mi v C++11 chybělo).- V
std::integral_constant
přibyloperator()
vracející konstantní hodnotu. - Přibyly konstantní verze
std::begin()
astd::end()
, pojmenovanéstd::cbegin()
astd::cend()
. - Přibyl
std::shared_timed_mutex
,std::shared_lock
astd::exchange()
- Přibyl std::integer_sequence.
To, že např. std::make_unique()
není už v C++11, je pravděpodobně jen přehlédnutí standardizační komise.
Odkazy: 1.
Co jsem přeskočil
K některým změnám jsem se v tomto příspěvku nedostal. Konkrétně se jedná o následující záležitosti.
Aktualizace 6.10.2015: Většinu z přeskočených novinek níže jsem popsal v navazujícím příspěvku.
- Umožnění inicializace agregátů obsahujících inicializátory datových složek. Detaily: 0.
- Heterogenní vyhledávání v asociativních kontejnerech. Detaily: 0, 1.
- Dealokace paměti s určením velikosti. Detaily: 0.
- Upřesnění požadavků na alokaci paměti. Detaily: 0.
- Upřesnění kontextově závislých konverzí. Detaily: 0.
Pokud víte o něčem dalším, co je v C++14 nového a nezmínil jsem to, určitě se ozvěte do komentáře. Výčet výše určitě nebude úplný.
Podpora v překladačích
Zde není nic neočekávaného. Clang 3.5 podporuje vše (status). V závěsu je GCC 4.9, které podporuje většinu z C++14 (status). Nejhůř je na tom tradičně Microsoft Visual Studio, které ani v nejnovějším technologickém preview CTP3 z 18.8.2014 nepodporuje ani celé C++11 a z C++14 podporuje jen tři záležitosti (status). Poslední stabilní verze (Visual Studio 2013 Update 3 z 4.8.2014) je na tom samozřejmě ještě hůře.
Závěrem
Jak je vidět, C++14 je spíše evoluce než revoluce. Pokud vám to používaný překladač dovolí, tak nevidím důvod, proč zůstávat u C++11 a nepřejít na C++14. Na větší změny si budeme muset počkat do C++1z. Možná se už konečně dočkáme konceptů :).
GCC 5
Jen mimochodem, "Snížení omezení kladených na constexpr" je poměrně dost práce, ale mělo by to být hotovo pro GCC 5. Tato verze by měla umět i concepts; prestože ty ještě nejsou v C++14.