13.14 — Dedukcja i dedukcja argumentów szablonu klasy (CTAD) przewodniki

Dedukcja argumentów szablonu klasy (CTAD) C++17

Począwszy od C++17, podczas tworzenia instancji obiektu na podstawie szablonu klasy, kompilator może wywnioskować typy szablonów z typów inicjatora obiektu (nazywa się to w skrócie dedukcją argumentów szablonu klasy lub CTAD ). Na przykład:

#include <utility> // for std::pair

int main()
{
    std::pair<int, int> p1{ 1, 2 }; // explicitly specify class template std::pair<int, int> (C++11 onward)
    std::pair p2{ 1, 2 };           // CTAD used to deduce std::pair<int, int> from the initializers (C++17)

    return 0;
}

CTAD jest wykonywane tylko wtedy, gdy nie istnieje żadna szablonowa lista argumentów. Dlatego oba poniższe przypadki są błędami:

#include <utility> // for std::pair

int main()
{
    std::pair<> p1 { 1, 2 };    // error: too few template arguments, both arguments not deduced
    std::pair<int> p2 { 3, 4 }; // error: too few template arguments, second argument not deduced

    return 0;
}

Nota autora

Wiele przyszłych lekcji na tej stronie korzysta z CTAD. Jeśli kompilujesz te przykłady przy użyciu standardu C++ 14 (lub starszego), pojawi się błąd dotyczący brakujących argumentów szablonu. Będziesz musiał jawnie dodać takie argumenty do przykładu, aby go skompilować.

Ponieważ CTAD jest formą dedukcji typu, możemy użyć sufiksów dosłownych, aby zmienić wydedukowany typ:

#include <utility> // for std::pair

int main()
{
    std::pair p1 { 3.4f, 5.6f }; // deduced to pair<float, float>
    std::pair p2 { 1u, 2u };     // deduced to pair<unsigned int, unsigned int>

    return 0;
}

Przewodniki po dedukowaniu argumentów w szablonie C++17

W większości przypadków CTAD działa od razu po wyjęciu z pudełka. Jednak w niektórych przypadkach kompilator może potrzebować dodatkowej pomocy w zrozumieniu, jak prawidłowo wydedukować argumenty szablonu.

Możesz być zaskoczony, gdy odkryjesz, że następujący program (który jest prawie identyczny z przykładem, w którym zastosowano std::pair powyżej) nie kompiluje się w C++17 (tylko):

// define our own Pair type
template <typename T, typename U>
struct Pair
{
    T first{};
    U second{};
};

int main()
{
    Pair<int, int> p1{ 1, 2 }; // ok: we're explicitly specifying the template arguments
    Pair p2{ 1, 2 };           // compile error in C++17 (okay in C++20)

    return 0;
}

Jeśli skompilujesz to w C++17, prawdopodobnie pojawi się błąd o „dedukcja argumentów szablonu klasy nie powiodła się” lub „nie można wydedukować argumentów szablonu” lub „Brak wykonalnego konstruktora lub przewodnika po dedukcjach”. Dzieje się tak, ponieważ w C++17 CTAD nie wie, jak wywnioskować argumenty szablonu dla szablonów klas zagregowanych. Aby rozwiązać ten problem, możemy udostępnić kompilatorowi przewodnik po dedukcjach, który informuje kompilator, jak wydedukować argumenty szablonu dla danego szablonu klasy.

Oto ten sam program z przewodnikiem po dedukcjach:

template <typename T, typename U>
struct Pair
{
    T first{};
    U second{};
};

// Here's a deduction guide for our Pair (needed in C++17 only)
// Pair objects initialized with arguments of type T and U should deduce to Pair<T, U>
template <typename T, typename U>
Pair(T, U) -> Pair<T, U>;
    
int main()
{
    Pair<int, int> p1{ 1, 2 }; // explicitly specify class template Pair<int, int> (C++11 onward)
    Pair p2{ 1, 2 };           // CTAD used to deduce Pair<int, int> from the initializers (C++17)

    return 0;
}

Ten przykład powinien zostać skompilowany w języku C++17.

Przewodnik po dedukcjach dla nasza Pair klasa jest dość prosta, ale przyjrzyjmy się bliżej, jak to działa.

// Here's a deduction guide for our Pair (needed in C++17 only)
// Pair objects initialized with arguments of type T and U should deduce to Pair<T, U>
template <typename T, typename U>
Pair(T, U) -> Pair<T, U>;

Najpierw używamy tej samej definicji typu szablonu, co w naszej klasie Pair . Ma to sens, ponieważ jeśli nasz przewodnik dedukcji ma powiedzieć kompilatorowi, jak wydedukować typy dla a Pair<T, U>, musimy zdefiniować, czym T i U są (typy szablonów). Po drugie, po prawej stronie strzałki znajduje się typ, który pomagamy kompilatorowi wydedukować. W tym przypadku chcemy, aby kompilator potrafił wydedukować argumenty szablonu dla obiektów typu Pair<T, U>, więc właśnie to tutaj umieściliśmy. Na koniec, po lewej stronie strzałki, mówimy kompilatorowi, jakiego rodzaju deklaracji ma szukać. W tym przypadku mówimy mu, aby szukał deklaracji jakiegoś obiektu o nazwie Pair z dwoma argumentami (jeden typu T, drugi typu U). Moglibyśmy to również zapisać jako Pair(T t, U u) (gdzie t i u są nazwami parametrów, ale ponieważ nie używamy t i u, nie musimy nadawać im nazw).

Łącząc to wszystko, mówimy kompilatorowi, że jeśli zobaczy deklarację a Pair z dwoma argumentami (z typy T i U ), powinien wywnioskować, że typem jest a Pair<T, U>.

Więc kiedy kompilator zobaczy definicję Pair p2{ 1, 2 }; w naszym programie, powie: „och, to jest deklaracja a Pair i istnieją dwa argumenty typu int i int, więc korzystając z przewodnika dedukcji, powinienem wywnioskować, że to jest a Pair<int, int>“.

Oto podobny przykład pary, która przyjmuje pojedynczy typ szablonu:

template <typename T>
struct Pair
{
    T first{};
    T second{};
};

// Here's a deduction guide for our Pair (needed in C++17 only)
// Pair objects initialized with arguments of type T and T should deduce to Pair<T>
template <typename T>
Pair(T, T) -> Pair<T>;

int main()
{
    Pair<int> p1{ 1, 2 }; // explicitly specify class template Pair<int> (C++11 onward)
    Pair p2{ 1, 2 };      // CTAD used to deduce Pair<int> from the initializers (C++17)

    return 0;
}

W tym przypadku nasz przewodnik po dedukcjach odwzorowuje a Pair(T, T) (a Pair z dwoma argumentami typu T) do a Pair<T>.

Wskazówka

C++ 20 dodał możliwość kompilatora automatycznego generowania przewodników dedukcji dla agregatów, więc przewodniki po dedukcjach powinny być dostarczane jedynie w celu zapewnienia zgodności z C++ 17.

Z tego powodu wersja Pair bez przewodników dedukcyjnych powinna zostać skompilowana w C++ 20.

std::pair (i inne standardowe typy szablonów bibliotek) są dostarczane z predefiniowanymi przewodnikami dedukcyjnymi, dlatego też nasz powyższy przykład, w którym std::pair kompiluje się dobrze w C++ 17 bez konieczności samodzielnego dostarczania przewodników dedukcyjnych.

Dla zaawansowanych czytelników

Nieagregowane nie potrzebujesz przewodników dedukcyjnych w C++17, ponieważ obecność konstruktora służy temu samemu celowi.

Wpisz parametry szablonu z wartościami domyślnymi

Tak jak parametry funkcji mogą mieć domyślne argumenty, tak parametry szablonu mogą otrzymać wartości domyślne. Będą one używane, gdy parametr szablonu nie jest jawnie określony i nie można go wydedukować.

Oto modyfikacja naszego Pair<T, U> programu szablonu klasy powyżej, z parametrami szablonu typów T i U domyślnie ustawionymi na type int:

template <typename T=int, typename U=int> // default T and U to type int
struct Pair
{
    T first{};
    U second{};
};

template <typename T, typename U>
Pair(T, U) -> Pair<T, U>;

int main()
{
    Pair<int, int> p1{ 1, 2 }; // explicitly specify class template Pair<int, int> (C++11 onward)
    Pair p2{ 1, 2 };           // CTAD used to deduce Pair<int, int> from the initializers (C++17)

    Pair p3;                   // uses default Pair<int, int>

    return 0;
}

Nasza definicja p3 nie określa jawnie typów parametrów szablonu typów, ani nie ma inicjatora dla tych typów, które można wydedukować od. Dlatego kompilator użyje typów określonych w ustawieniach domyślnych, co oznacza p3 będzie typu Pair<int, int>.

CTAD nie działa z inicjalizacją elementu niestatycznego

Podczas inicjowania elementu typu klasy przy użyciu niestatycznej inicjalizacji elementu członkowskiego, CTAD nie będzie działać w tym kontekście. Wszystkie argumenty szablonu muszą być wyraźnie określone:

#include <utility> // for std::pair

struct Foo
{
    std::pair<int, int> p1{ 1, 2 }; // ok, template arguments explicitly specified
    std::pair p2{ 1, 2 };           // compile error, CTAD can't be used in this context
};

int main()
{
    std::pair p3{ 1, 2 };           // ok, CTAD can be used here
    return 0;
}

CTAD nie działa z parametrami funkcji

CTAD oznacza szablon klasy argument dedukcję, a nie szablon klasy parametr dedukcję, więc wydedukuje tylko typ argumentów szablonu, a nie parametry szablonu.

Dlatego CTAD nie można używać w parametrach funkcji.

#include <iostream>
#include <utility>

void print(std::pair p) // compile error, CTAD can't be used here
{
    std::cout << p.first << ' ' << p.second << '\n';
}

int main()
{
    std::pair p { 1, 2 }; // p deduced to std::pair<int, int>
    print(p);

    return 0;
}

W takich przypadkach należy zamiast tego użyć szablonu:

#include <iostream>
#include <utility>

template <typename T, typename U>
void print(std::pair<T, U> p)
{
    std::cout << p.first << ' ' << p.second << '\n';
}

int main()
{
    std::pair p { 1, 2 }; // p deduced to std::pair<int, int>
    print(p);

    return 0;
}
guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
87 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze