Szablony

Aliasy szablonów

Aliasy typów

W C++11 deklaracja using może zostać użyta do tworzenia bardziej czytelnych aliasów dla typów - zamiennik dla typedef.

using CarID = int;

using Func = int(*)(double, double);

using DictionaryDesc = std::map<std::string, std::string, std::greater<std::string>>;

Aliasy szablonów

Aliasy typów mogą być parametryzowane typem. Można je wykorzystać do tworzenia częściowo związanych typów szablonowych.

template <typename T>
using StrKeyMap = std::map<std::string, T>;

StrKeyMap<int> my_map; // std::map<std::string, int>

Parametrem aliasu typu szablonowego może być także stała znana w czasie kompilacji:

template <std::size_t N>
using StringArray = std::array<std::string, N>;

StringArray<255> arr1;

Aliasy szablonów nie mogą być specjalizowane.

template <typename T>
using MyAllocList = std::list<T, MyAllocator>;

template <typename T>
using MyAllocList = std::list<T*, MyAllocator>; // error

Przykład utworzenia aliasu dla szablonu klasy smart pointer’a:

template <typename Stream>
struct StreamDeleter
{
    void operator()(Stream* os)
    {
        os->close();
        delete os;
    }
}

template <typename Stream>
using StreamPtr = std::unique_ptr<Stream, StreamDeleter<Stream>>;

// ...
{
    StreamPtr<ofstream> p_log(new ofstream("log.log"));
    *p_log << "Log statement";
}

Aliasy i typename

Od C++14 biblioteka standardowa używa aliasów dla wszystkich cech typów, które zwracają typ.

template <typename T>
using is_void_t = typename is_void<T>::type;

W rezultacie kod odwołujący się do cechy:

typename is_void<T>::type

możemy uprościć do:

is_void_t<T>;

Szablony zmiennych

W C++14 zmienne mogą być parametryzowane przy pomocy typu. Takie zmienne nazywamy zmiennymi szablonowymi (variable templates).

template<typename T>
constexpr T pi{3.1415926535897932385};

Aby użyć zmiennej szablonowej, należy podać jej typ:

std::cout << pi<double> << '\n';
std::cout << pi<float> << '\n';

Parametrami zmiennych szablonowych mogą być stałe znane na etapie kompilacji:

template<int N>
std::array<int, N> arr{};

int main()
{
    arr<10>[0] = 42; // sets first element of global arr

    for (std::size_t i = 0; i < arr<10>.size(); ++i)
    {
        // uses values set in arr
        std::cout << arr<10>[i] << '\n';
    }
}

Zmienne szablonowe a jednostki translacji

Deklaracja zmiennych szablonowych może być używana w innych jednostkach translacji.

  • plik - header.hpp

    template<typename T> T val{};     // zero initialized value
    
  • plik - „unit1.cpp”

    #include "header.hpp"
    
    int main()
    {
        val<long> = 42;
        print();
    }
    
  • plik - „unit2.cpp”

    void print()
    {
        std::cout << val<long> << '\n'; // OK: prints 42
    }
    

Variadic templates

W C++11 szablony mogą akceptować dowolną ilość (również zero) parametrów. Jest to możliwe dzięki użyciu specjalnego grupowego parametru szablonu tzw. parameter pack, który reprezentuje wiele lub zero parametrów szablonu.

Parameter pack

Parameter pack może być

  • grupą parametrów szablonu

    template<typename... Ts> // template parameter pack
    class tuple
    {
        //...
    };
    
    tuple<int, double, string&> t1; // 3 arguments: int, double, string&
    tuple<> empty_tuple; // 0 arguments
    
  • grupą argumentów funkcji szablonowej

    template <typename T, typename... Args>
    shared_ptr<T> make_shared(Args&&... params)
    {
        //...
    }
    
    auto sptr = make_shared<Gadget>(10); // Args as template param: int
                                         // Args as function param: int&&
    

Rozpakowanie paczki parametrów

Podstawową operacją wykonywaną na grupie parametrów szablonu jest rozpakowanie jej za pomocą operatora ... (tzw. pack expansion).

template <typaname... Ts>  // template  parameter pack
struct X
{
    tuple<Ts...> data;  // pack expansion
};

Rozpakowanie paczki parametrów (pack expansion) może zostać zrealizowane przy pomocy wzorca zakończonego elipsą ...:

template <typaname... Ts>  // template  parameter pack
struct XPtrs
{
    tuple<Ts const*...> ptrs;  // pack expansion
};

XPtrs<int, string, double> ptrs; // contains tuple<int const*, string const*, double const*>

Najczęstszym przypadkiem użycia wzorca przy rozpakowaniu paczki parametrów jest implementacja perfect forwarding’u:

template <typename... Args>
void make_call(Args&&... params)
{
    callable(forward<Args>(params)...);
}

Idiom Head/Tail

Praktyczne użycie variadic templates wykorzystuje często idiom Head/Tail (znany również First/Rest).

Idiom ten polega na zdefiniowaniu wersji szablonu akceptującego dwa parametry: - pierwszego Head - drugiego Tail w postaci paczki parametrów

W implementacji wykorzystany jest parametr (lub argument) typu Head, po czym rekursywnie wywołana jest implementacja dla rozpakowanej paczki parametrów typu Tail.

Dla szablonów klas idiom wykorzystuje specjalizację częściową i szczegółową (do przerwania rekurencji):

template <typename... Types>
struct Count;

template <typename Head, typename... Tail>
struct Count<Head, Tail...>
{
    constexpr static int value = 1 + Count<Tail...>::value; // expansion pack
};

template <>
struct Count<>
{
    constexpr static int value = 0;
};

//...
static_assert(Count<int, double, string&>::value == 3, "must be 3");

W przypadku szablonów funkcji rekurencja może być przerwana przez dostarczenie odpowiednio przeciążonej funkcji. Zostanie ona w odpowiednim momencie rozwijania rekurencji wywołana.

void print()
{}

template <typename T, typename... Tail>
void print(const T& arg1, const Tail&... params)
{
    cout << arg1 << endl;
    print(params...); // function parameter pack expansion
}

Operator sizeof…

Operator sizeof... umożliwia odczytanie na etapie kompilacji ilości parametrów w grupie.

template <typename... Types>
struct VerifyCount
{
    static_assert(Count<Types...>::value == sizeof...(Types),
                    "Error in counting number of parameters");
};

Forwardowanie wywołań funkcji

Variadic templates są niezwykle przydatne do forwardowania wywołań funkcji.

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... params)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(params)...);
}

Ograniczenia paczek parametrów

Klasa szablonowa może mieć tylko jedną paczkę parametrów i musi ona zostać umieszczona na końcu listy parametrów szablonu:

template <size_t... Indexes, typename... Ts>  // error
class Error;

Można obejść to ograniczenie w następujący sposób:

template <size_t... Indexes> struct IndexSequence {};

template <typename Indexes, typename Ts...>
class Ok;

Ok<IndexSequence<1, 2, 3>, int, char, double> ok;

Funkcje szablonowe mogą mieć więcej paczek parametrów:

template <int... Factors, typename... Ts>
void scale_and_print(Ts const&... args)
{
    print(ints * args...);
}

scale_and_print<1, 2, 3>(3.14, 2, 3.0f);  // calls print(1 * 3.14, 2 * 2, 3 * 3.0)

Uwaga! Wszystkie paczki w tym samym wyrażeniu rozpakowującym muszą mieć taki sam rozmiar.

scale_and_print<1, 2>(3.14, 2, 3.0f);  // error

„Nietypowe” paczki parametrów

  • Podobnie jak w przypadku innych parametrów szablonów, paczka parametrów nie musi być paczką typów, lecz może być paczką stałych znanych na etapie kompilacji
template <size_t... Values>
struct MaxValue;  // primary template declaration

template <size_t First, size_t... Rest>
struct MaxValue<First, Rest...>
{
    static constexpr size_t rvalue = MaxValue<Rest...>::value;
    static constexpr size_t value = (First < rvalue) ? rvalue : First;
};

template <size_t Last>
struct MaxValue<Last>
{
    static constexpr size_t value = Last;  // termination of recursive expansion
};
static_assert(MaxValue<1, 5345, 3, 453, 645, 13>::value == 5345, "Error");

Variadic Mixins

Variadic templates mogą być skutecznie wykorzystane do implementacji klas mixin

#include <vector>
#include <string>

template <typename... Mixins>
class X : public Mixins...
{
public:
    X(Mixins&&... mixins) : Mixins(mixins)...
    {}
};

Klasa taka może zostać wykorzystana później w następujący sposób:

X<std::vector<int>, std::string> x({ 1, 2, 3 }, "text");

x.std::string::size();
(unsigned long) 4
x.std::vector<int>::size();
(unsigned long) 3

Fold expressions (C++17)

Wyrażenia fold w językach funkcjonalnych

Koncept redukcji jest jednym z podstawowych pojęć w językach funkcjonalnych.

Fold w językach funkcjonalnych to rodzina funkcji wyższego rzędu zwana również reduce, accumulate, compress lub inject. Funkcje fold przetwarzają rekursywnie uporządkowane kolekcje danych (listy) w celu zbudowania końcowego wyniku przy pomocy funkcji (operatora) łączącej elementy.

Dwie najbardziej popularne funkcje z tej rodziny to fold (fold left) i foldr (fold right).

Przykład:

Redukcja listy [1, 2, 3, 4, 5] z użyciem operatora (+):

  • użycie funkcji fold - redukcja od lewej do prawej

    fold (+) 0 [1..5]
    
    (((((0 + 1) + 2) + 3) + 4) + 5)
    
  • użycie funkcji foldr - redukcja od prawej do lewej

    foldr (+) 0 [1..5]
    
    (1 + (2 + (3 + (4 + (5 + 0)))))
    

Redukcja w C++98 - std::accumulate

W C++ redukcja jest obecna poprzez implementację algorytmu std::accumulate.

#include <vector>
#include <numeric>
#include <string>

using namespace std::string_literals;

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

std::accumulate(std::begin(vec), std::end(vec), "0"s,
                   [](const std::string& reduction, int item) {
                       return "("s + reduction +  " + "s + std::to_string(item) + ")"s; });
(((((0 + 1) + 2) + 3) + 4) + 5)

Wyrażenia fold

Wyrażenia typu fold umożliwiają uproszczenie rekurencyjnych implementacji dla zmiennej liczby argumentów szablonu.

Przykład z wariadyczną funkcją sum(1, 2, 3, 4, 5) z wykorzystaniem fold expressions może być w C++17 zaimplementowany następująco:

template <typename... Args>
auto sum(Args&&... args)
{
    return (... + args);
}
sum(1, 2, 3, 4, 5);
(int) 15

Składnia wyrażeń fold

Niech \(e = e_1, e_2, \dotso, e_n\) będzie wyrażeniem, które zawiera nierozpakowany parameter pack i \(\otimes\) jest operatorem fold, wówczas wyrażenie fold ma postać:

  • Unary left fold

    \((\dotso\; \otimes\; e)\)

który jest rozwijany do postaci \((((e_1 \otimes e_2) \dotso ) \otimes e_n)\)

  • Unary right fold

    \((e\; \otimes\; \dotso)\)

który jest rozwijany do postaci \((e_1 \otimes ( \dotso (e_{n-1} \otimes e_n)))\)

Jeśli dodamy argument nie będący paczką parametrów do operatora ..., dostaniemy dwuargumentową wersję wyrażenia fold. W zależności od tego po której stronie operatora ... dodamy dodatkowy argument otrzymamy:

  • Binary left fold

    \((a \otimes\; \dotso\; \otimes\; e)\)

który jest rozwijany do postaci \((((a \otimes e_1) \dotso ) \otimes e_n)\)

  • Binary right fold

    \((e\; \otimes\; \dotso\; \otimes\; a)\)

który jest rozwijany do postaci \((e_1 \otimes ( \dotso (e_n \otimes a)))\)

Operatorem \(\otimes\) może być jeden z poniższych operatorów C++:

+  -  *  /  %  ^  &  |  ~  =  <  >  <<  >>
+=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=
==  !=  <=  >=  &&  ||  ,  .*  ->*

Elementy identycznościowe

Operacja fold dla pustej paczki parametrów (parameter pack) jest ewaluowana do określonej wartości zależnej od rodzaju zastosowanego operatora. Zbiór operatorów i ich rozwinięć dla pustej listy parametrów prezentuje tabela:

Operator Wartość zwracana jako element identycznościowy
\(\&\&\) true
\(\mid\mid\) false
\(,\) void()

Jeśli operacja fold jest ewaluowana dla pustej paczki parametrów dla innego operatora, program jest nieprawidłowo skonstruowany (ill-formed).

Przykłady zastosowań wyrażeń fold

Wariadyczna funkcja przyjmująca dowolną liczbę argumentów konwertowalnych do wartości logicznych i zwracająca ich iloczyn logiczny (operator &&):

template <typename... Args>
bool all_true(Args... args)
{
    return (... && args);
}
bool result = all_true(true, true, false, true);
(bool) false

Funkcja print() wypisująca przekazane argumenty. Implementacja wykorzystuje wyrażenie binary left fold dla operatora <<:

#include <iostream>

template <typename... Args>
void print(Args&&... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, 2, 3, 4);
1234

Iteracja po elementach różnych typów:

#include <iostream>

struct Window {
    void show() const { std::cout << "showing Window\n"; }
};

struct Widget {
    void show() const { std::cout << "showing Widget\n"; }
};

struct Toolbar {
    void show() const { std::cout << "showing Toolbar\n"; }
};
Window wnd;
Widget widget;
Toolbar toolbar;

auto printer = [](const auto&... args) { args.show(), ...); };

printer(wnd, widget, toolbar);
showing Window
showing Widget
showing Toolbar

Implementacja wariadycznej wersji algorytmu foreach() z wykorzystaniem funkcji std::invoke():

#include <iostream>

template <typename F, typename... Args>
auto invoke(F&& f, Args&&... args)
{
    return std::forward<F>(f)(std::forward<Args>(args)...);
}

struct Printer
{
    template <typename T>
    void operator()(T&& arg) const { std::cout << arg; }
};
#include <string>

using namespace std::literals;

auto foreach = [](auto&& fun, auto&&... args) {
    (..., invoke(fun, std::forward<decltype(args)>(args));
};

foreach(Printer{}, 1, " - one; ", 3.14, " - pi;"s);
1 - one; 3.14 - pi

Dedukcja argumentów szablonu dla klas - CTAD (C++17)

C++17 wprowadza mechanizm dedukcji argumentów szablonu klasy (Class Template Argument Deduction). Typy parametrów szablonu klasy mogą być dedukowane na podstawie argumentów przekazanych do konstruktora tworzonego obiektu.

template <typename T>
class complex
{
    T re_, img_;
public:
    complex(T re, T img) : re_{re}, img_{img}
    {}
};

auto c1 = complex<int>{5, 2}; // OK - all versions of C++ standard

auto c2 = complex{5, 3}; // OK since C++17 - compiler deduces complex<int>

auto c3 = complex(5.1, 6.5); // OK in C++17 - compiler deduces complex<double>

auto c4 = complex{5, 4.1}; // ERROR - args don't have the same type
auto c5 = complex(5, 4.1); // ERROR - args don't have the same type

Ostrzeżenie

Nie można częściowo dedukować argumentów szablonu klasy. Należy wyspecyfikować lub wydedukować wszystkie parametry z wyjątkiem parametrów domyślnych.

Praktyczny przykład dedukcji argumentów szablonu klasy:

std::mutex mtx;

std::lock_guard lk{mtx}; // deduces std::lock_guard<std::mutex>

Podpowiedzi dedukcyjne (deduction guides)

C++17 umożliwia tworzenie podpowiedzi dla kompilatora, jak powinny być dedukowane typy parametrów szablonu klasy na podstawie wywołania odpowiedniego konstruktora.

Daje to możliwość poprawy/modyfikacji domyślnego procesu dedukcji.

Dla szablonu:

template <typename T>
class S
{
private:
    T value;
public:
    S(T v) : value(v)
    {}
};

Podpowiedź dedukcyjna musi zostać umieszczona w tym samym zakresie (przestrzeni nazw) i może mieć postać:

template <typename T> S(T) -> S<T>; // deduction guide

gdzie:

  • S<T> to tzw. typ zalecany (guided type)
  • nazwa podpowiedzi dedukcyjnej musi być niekwalifikowaną nazwą klasy szablonowej zadeklarowanej wcześniej w tym samym zakresie
  • typ zalecany podpowiedzi musi odwoływać się do identyfikatora szablonu (template-id), do którego odnosi się podpowiedź

Użycie podpowiedzi:

S x{12}; // OK -> S<int> x{12};
S y(12); // OK -> S<int> y(12);
auto z = S{12}; // OK -> auto z = S<int>{12};
S s1(1), s2{2}; // OK -> S<int> s1(1), s2{2};
S s3(42), s4{3.14}; // ERROR

W deklaracji S x{12}; specyfikator S jest nazywany symbolem zastępczym dla klasy (placeholder class type).

W przypadku użycia symbolu zastępczego dla klasy, nazwa zmiennej musi zostać podana jako następny element składni. W rezultacie poniższa deklaracja jest błędem składniowym:

S* p = &x; // ERROR - syntax not permitted

Dany szablon klasy może mieć wiele konstruktorów oraz wiele podpowiedzi dedukcyjnych:

template <typename T>
struct Data
{
    T value;

    using type1 = T;

    Data(const T& v)
        : value(v)
    {
    }

    template <typename ItemType>
    Data(initializer_list<ItemType> il)
        : value(il)
    {
    }
};

template <typename T>
Data(T)->Data<T>;

template <typename T>
Data(initializer_list<T>)->Data<vector<T>>;

Data(const char*) -> Data<std::string>;

//...

Data d1("hello"); // OK -> Data<string>

const int tab[10] = {1, 2, 3, 4};
Data d2(tab); // OK -> Data<const int*>

Data d3 = 3; // OK -> Data<int>

Data d4{1, 2, 3, 4}; // OK -> Data<vector<int>>

Data d5 = {1, 2, 3, 4}; // OK -> Data<vector<int>>

Data d6 = {1}; // OK -> Data<vector<int>>

Data d7(d6); // OK - copy by default rule -> Data<vector<int>>

Data d8{d6, d7}; // OK -> Data<vector<Data<vector<int>>>>

Podpowiedzi dedukcyjne nie są szablonami funkcji - służą jedynie dedukowaniu argumentów szablonu i nie są wywoływane. W rezultacie nie ma znaczenia czy argumenty w deklaracjach dedukcyjnych są przekazywane przez referencje, czy nie.

template <typename T>
struct X
{
    //...
};

template <typename T>
struct Y
{
    Y(const X<T>&);
    Y(X<T>&&);
};

template <typename T> Y(X<T>) -> Y<T>; // deduction guide without references

W powyższym przykładzie podpowiedź dedukcyjna nie odpowiada dokładnie sygnaturom konstruktorów przeciążonych. Nie ma to znaczenia, ponieważ jedynym celem podpowiedzi jest umożliwienie dedukcji typu, który jest parametrem szablonu. Dopasowanie wywołania przeciążonego konstruktora odbywa się później.

Niejawne podpowiedzi dedukcyjne

Ponieważ często podpowiedź dedukcyjna jest potrzebna dla każdego konstruktora klasy, standard C++17 wprowadza mechanizm niejawnych podpowiedzi dedukcyjnych (implicit deduction guides). Działa on w następujący sposób:

  • Lista parametrów szablonu dla podpowiedzi zawiera listę parametrów z szablonu klasy - w przypadku szablonowego konstruktora klasy kolejnym elementem jest lista parametrów szablonu konstruktora klasy
  • Parametry „funkcyjne” podpowiedzi są kopiowane z konstruktora lub konstruktora szablonowego
  • Zalecany typ w podpowiedzi jest nazwą szablonu z argumentami, które są parametrami szablonu wziętymi z klasy szablonowej

Dla klasy szablonowej rozważanej powyżej:

template <typename T>
class S
{
private:
    T value;
public:
    S(T v) : value(v)
    {}
};

niejawna podpowiedź dedukcyjna będzie wyglądać następująco:

template <typename T> S(T) -> S<T>; // implicit deduction guide

W rezultacie programista nie musi implementować jej jawnie.

Specjalny przypadek dedukcji argumentów klasy szablonowej

Rozważmy następujący przypadek dedukcji:

S x{42}; // x has type S<int>

S y{x};
S z(x);

W obu przypadkach dedukowany typ zmiennych y i z to S<int>. Mechanizm dedukcji argumentów klasy szablonowej dedukuje typ taki sam jak typ oryginalnego obiektu a następnie wywoływany jest konstruktor kopiujący.

  • dla deklaracji S<T> x; S{x} dedukuje typ: S<T>{x} zamiast S<S<T>>{x}

W niektórych przypadkach może być to zaskakujące i kontrowersyjne:

std::vector v{1, 2, 3}; // vector<int>
std::vector data1{v, v}; // vector<vector<int>>
std::vector data2{v}; // vector<int>!

W powyższym kodzie dedukcja argumentów szablonu vector zależy od ilości argumentów przekazanych do konstruktora!

Agregaty a dedukcja argumentów

Jeśli szablon klasy jest agregatem, to mechanizm automatycznej dedukcji argumentów szablonu wymaga napisania jawnej podpowiedzi dedukcyjnej.

Bez podpowiedzi dedukcyjnej dedukcja dla agregatów nie działa:

template <typename T>
struct Aggregate1
{
    T value;
};

Aggregate1 agg1{8}; // ERROR
Aggregate1 agg2{"eight"}; // ERROR
Aggregate1 agg3 = 3.14; // ERROR

Gdy napiszemy dla agregatu podpowiedź, to możemy zacząć korzystać z mechanizmu dedukcji:

template <typename T>
struct Aggregate2
{
    T value;
};

template <typename T>
Aggregate2(T) -> Aggregate2<T>;

Aggregate2 agg1{8}; // OK -> Aggregate2<int>
Aggregate2 agg2{"eight"}; // OK -> Aggregate2<const char*>
Aggregate2 agg3 = { 3.14 }; // OK -> Aggregate2<double>

Podpowiedzi dedukcyjne w bibliotece standardowej

Dla wielu klas szablonowych z biblioteki standardowej dodano podpowiedzi dedukcyjne w celu ułatwienia tworzenia instancji tych klas.

std::pair<T>

Dla pary STL dodana w standardzie podpowiedź to:

template<class T1, class T2>
pair(T1, T2) -> pair<T1, T2>;

pair p1(1, 3.14); // -> pair<int, double>

pair p2{3.14f, "text"s}; // -> pair<float, string>

pair p3{3.14f, "text"}; // -> pair<float, const char*>

int tab[3] = { 1, 2, 3 };
pair p4{1, tab}; // -> pair<int, int*>

std::tuple<T…>

Szablon std::tuple jest traktowany podobnie jak std::pair:

template<class... UTypes>
tuple(UTypes...) -> tuple<UTypes...>;

template<class T1, class T2>
tuple(pair<T1, T2>) -> tuple<T1, T2>;

//... other deduction guides working with allocators

int x = 10;
const int& cref_x = x;

tuple t1{x, &x, cref_x, "hello", "world"s}; -> tuple<int, int*, int, const char*, string>

std::optional<T>

Klasa std::optional jest traktowana podobnie do pary i krotki.

template<class T> optional(T) -> optional<T>;

optional o1(3); // -> optional<int>
optional o2 = o1; // -> optional<int>

Inteligentne wskaźniki

Dedukcja dla argumentów konstruktora będących wskaźnikami jest zablokowana:

int* ptr = new int{5};
unique_ptr uptr{ip}; // ERROR - ill-formed (due to array type clash)

Wspierana jest dedukcja przy konwersjach:

  • z weak_ptr/unique_ptr do shared_ptr:

    template <class T> shared_ptr(weak_ptr<T>) ->  shared_ptr<T>;
    template <class T, class D> shared_ptr(unique_ptr<T, D>) ->  shared_ptr<T>;
    
  • z shared_ptr do weak_ptr

    template<class T> weak_ptr(shared_ptr<T>) -> weak_ptr<T>;
    
unique_ptr<int> uptr = make_unique<int>(3);

shared_ptr sptr = move(uptr); -> shared_ptr<int>

weak_ptr wptr = sptr; // -> weak_prt<int>

shared_ptr sptr2{wptr}; // -> shared_ptr<int>

std::function

Dozwolone jest dedukowanie sygnatur funkcji dla std::function:

int add(int x, int y)
{
    return x + y;
}

function f1 = &add;
assert(f1(4, 5) == 9);

function f2 = [](const string& txt) { cout << txt << " from lambda!" << endl; };
f2("Hello");

Kontenery i sekwencje

Dla kontenerów standardowych dozwolona jest dedukcja typu kontenera dla konstruktora akceptującego parę iteratorów:

vector<int> vec{ 1, 2, 3 };
list lst(vec.begin(), vec.end()); // -> list<int>

Dla std::array dozwolona jest dedukcja z sekwencji:

std::array arr1 { 1, 2, 3 }; // -> std::array<int, 3>