Jste zde

Co je nového v C++14

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.

Odkazy: 0, 1, 2, 3, 4.

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.

Odkazy: 0, 1, 2, 3.

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; };

Odkazy: 0, 1, 2.

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 a do-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;
}

Odkazy: 0, 1, 2.

Š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;
}

Odkazy: 0, 1, 2.

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).

Odkazy: 0, 1, 2.

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

Odkazy: 0, 1.

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.

Odkazy: 0, 1, 3, 4.

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).

Detaily: 0, 1.

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:

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:

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ů :).

Komentáře

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.

Přidat komentář