10.9 -- Dedukcja typu dla funkcji

Rozważ następujący program:

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

Gdy ta funkcja jest kompilowana, kompilator ustali, że x + ywylicza int, a następnie upewnia się, że typ zwracanej wartości jest zgodny z zadeklarowanym typem zwracanym funkcji (lub że typ wartości zwracanej można przekonwertować na zadeklarowany typ zwracany).

Dedukcja typu zwracanego za pomocą auto

Ponieważ kompilator musi już wywnioskować typ zwracany z instrukcji return (aby mieć pewność, że wartość może zostać przekonwertowana na zadeklarowany typ zwracany przez funkcję), w C++14 słowo kluczowe auto zostało rozszerzone, aby móc dedukować typ zwracany przez funkcję. Działa to poprzez użycie słowa kluczowego auto w miejscu typu zwracanego przez funkcję.

Na przykład:

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

Ponieważ instrukcja return zwraca int wartość, kompilator wywnioskuje, że typem zwracanym przez tę funkcję jest int.

W przypadku używania typu zwracanego auto wszystkie instrukcje return w ramach funkcji muszą zwracać wartości tego samego typu, w przeciwnym razie wystąpi błąd. Na przykład:

auto someFcn(bool b)
{
    if (b)
        return 5; // return type int
    else
        return 6.7; // return type double
}

W powyższej funkcji dwie instrukcje return zwracają wartości różnych typów, więc kompilator zgłosi błąd.

Jeśli z jakiegoś powodu taki przypadek jest pożądany, możesz albo jawnie określić typ powrotu dla swojej funkcji (w takim przypadku kompilator spróbuje niejawnie przekonwertować wszelkie niepasujące wyrażenia powrotu na jawny typ powrotu), albo możesz jawnie przekonwertować wszystkie instrukcje return na ten sam typ. W powyższym przykładzie to drugie można zrobić, zmieniając 5 Do 5.0, ale static_cast można go również użyć w przypadku typów innych niż dosłowne.

Korzyści dedukcji typu zwracanego

Największą zaletą dedukcji typu zwracanego jest to, że kompilator wydedukuje typ zwracany przez funkcję, co eliminuje ryzyko niedopasowanego typu zwrotu (zapobiegając nieoczekiwanym konwersje).

Może to być szczególnie przydatne, gdy typ zwracany przez funkcję jest delikatny (przypadki, gdy typ zwracany prawdopodobnie ulegnie zmianie w przypadku zmiany implementacji). W takich przypadkach jednoznaczne określenie typu zwracanego oznacza konieczność aktualizacji wszystkich odpowiednich typów zwracanych, gdy w implementacji wprowadzana jest istotna zmiana. Jeśli będziemy mieli szczęście, kompilator będzie zgłaszał błąd, dopóki nie zaktualizujemy odpowiednich typów zwracanych wartości. Jeśli nie będziemy mieli szczęścia, otrzymamy ukryte konwersje tam, gdzie ich nie chcemy.

W innych przypadkach typ zwracany przez funkcję może być długi i złożony lub nie być aż tak oczywisty. W takich przypadkach auto można użyć do uproszczenia:

// let compiler determine the return type of unsigned short + char
auto add(unsigned short x, char y)
{
    return x + y;
}

Omawiamy ten przypadek nieco szerzej (i jak wyrazić rzeczywisty typ zwracanej funkcji) w lekcji 11.8 -- Szablony funkcji z wieloma typami szablonów.

Wady dedukcji typu zwrotu

Istnieją dwie główne wady dedukcji typu zwrotu:

  1. Funkcje korzystające ze zwrotu auto typ musi być w pełni zdefiniowany, zanim będzie można go użyć (deklaracja forward nie jest wystarczająca). Na przykład:
#include <iostream>

auto foo();

int main()
{
    std::cout << foo() << '\n'; // the compiler has only seen a forward declaration at this point

    return 0;
}

auto foo()
{
    return 5;
}

Na komputerze autora powoduje to następujący błąd kompilacji:

error C3779: 'foo': a function that returns 'auto' cannot be used before it is defined.

To ma sens: deklaracja forward nie zawiera wystarczających informacji, aby kompilator mógł wydedukować typ zwracany przez funkcję. Oznacza to, że normalne funkcje zwracające auto są zwykle wywoływane tylko z poziomu pliku, w którym są zdefiniowane.

  1. Gdy używasz dedukcji typu z obiektami, inicjator jest zawsze obecny jako część tej samej instrukcji, więc określenie, jaki typ będzie dedukowany, zwykle nie jest zbyt uciążliwe. W przypadku dedukcji typu funkcji tak nie jest — prototyp funkcji nie wskazuje, jaki typ faktycznie zwraca funkcja. Dobre programistyczne IDE powinno jasno wyjaśniać, jaki jest wydedukowany typ funkcji, ale w przypadku braku takiego dostępu użytkownik musiałby w rzeczywistości zagłębić się w samą treść funkcji, aby określić, jaki typ zwróciła funkcja. Ryzyko popełnienia błędów jest wyższe. Generalnie wolimy wyraźnie mówić o typach, które są częścią interfejsu (deklaracja funkcji jest interfejsem).

W przeciwieństwie do dedukcji typu w przypadku obiektów, nie ma tak dużego konsensusu co do najlepszych praktyk w zakresie dedukcji typu zwracanego przez funkcję. Zalecamy ogólnie unikać dedukcji typu zwrotu dla funkcji.

Najlepsza praktyka

Preferuj jawne typy zwracanych wartości zamiast dedukcji typu zwracanych wartości (z wyjątkiem przypadków, gdy typ zwracany jest nieistotny, trudny do wyrażenia lub delikatny).

Składnia końcowego typu zwracanego

Klasa auto słowa kluczowego może być również użyte do deklarowania funkcji przy użyciu końcowa składnia powrotu, gdzie typ zwracany jest określony po pozostałej części funkcji prototyp.

Rozważ następującą funkcję:

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

Używając składni końcowego zwrotu, można to równoważnie zapisać w następujący sposób:

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

W tym przypadku auto nie wykonuje dedukcji typów — jest to tylko część składni polegająca na użyciu końcowego typu zwracanego.

Dlaczego miałbyś tego używać? Oto kilka powodów:

  1. W przypadku funkcji ze złożonymi typami zwracanych końcowy typ zwracany może ułatwić odczytanie tej funkcji:
#include <type_traits> // for std::common_type

std::common_type_t<int, double> compare(int, double);         // harder to read (where is the name of the function in this mess?)
auto compare(int, double) -> std::common_type_t<int, double>; // easier to read (we don't have to read the return type unless we care)
  1. Składnia końcowego typu zwracanego może zostać użyta do wyrównania nazw funkcji, co ułatwia czytanie kolejnych deklaracji funkcji:
auto add(int x, int y) -> int;
auto divide(double x, double y) -> double;
auto printSomething() -> void;
auto generateSubstring(const std::string &s, int start, int len) -> std::string;

Dla zaawansowanych czytelników

  1. Jeśli mamy funkcję, której typ zwracany musi być wydedukowany na podstawie typu parametrów funkcji, normalny typ zwracany nie wystarczy, ponieważ kompilator ma nie widziałem jeszcze parametrów w tym momencie.
#include <type_traits>
// note: decltype(x) evaluates to the type of x

std::common_type_t<decltype(x), decltype(y)> add(int x, double y);         // Compile error: compiler hasn't seen definitions of x and y yet
auto add(int x, double y) -> std::common_type_t<decltype(x), decltype(y)>; // ok
  1. Składnia końcowego powrotu jest również wymagana w przypadku niektórych zaawansowanych funkcji C++, takich jak lambdy (które omówimy w lekcji 20.6 -- Wprowadzenie do lambd (funkcji anonimowych)).

Na razie zalecamy dalsze używanie tradycyjnej składni zwracanej funkcji, z wyjątkiem sytuacji, które wymagają składni końcowego powrotu.

Dedukcji typów nie można stosować w przypadku typów parametrów funkcji

Wielu nowych programistów, którzy dowiedz się o dedukcji typu, spróbuj czegoś takiego:

#include <iostream>

void addAndPrint(auto x, auto y)
{
    std::cout << x + y << '\n';
}

int main()
{
    addAndPrint(2, 3); // case 1: call addAndPrint with int parameters
    addAndPrint(4.5, 6.7); // case 2: call addAndPrint with double parameters

    return 0;
}

Niestety, dedukcja typów nie działa dla parametrów funkcji, a przed wersją C++ 20 powyższy program nie zostanie skompilowany (pojawi się komunikat o błędzie, że parametry funkcji nie mogą mieć typu automatycznego).

W C++20, auto słowo kluczowe zostało rozszerzone, aby powyższy program mógł się poprawnie kompilować i działać - jednak auto nie odwołuje się do dedukcji typów w tym przypadku. Zamiast tego uruchamia inną funkcję o nazwie function templates , która została zaprojektowana do obsługi takich przypadków.

Powiązana treść

W lekcji 11.6 -- Szablony funkcjiprzedstawiamy szablony funkcji i omawiamy użycie auto w kontekście szablonów funkcji na lekcji 11.8 -- Szablony funkcji z wieloma typami szablonów.

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:  
55 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze