11.9 — Nietypowe parametry szablonu

W poprzednich lekcjach omawialiśmy sposób tworzenia szablonów funkcji korzystających z parametrów szablonu typów. Parametr szablonu typu służy jako element zastępczy rzeczywistego typu, który jest przekazywany jako argument szablonu.

Chociaż parametry szablonu typu są zdecydowanie najczęściej używanym typem parametru szablonu, istnieje inny rodzaj parametru szablonu, o którym warto wiedzieć: parametr szablonu inny niż typ.

Szablon inny niż typ parametry

nietypowym parametrem szablonu jest parametrem szablonu o stałym typie, który służy jako symbol zastępczy wartości constexpr przekazanej jako argument szablonu.

Parametr szablonu inny niż typ może należeć do dowolnego z następujących typów:

  • Typ całkowity
  • Wyliczenie typ
  • std::nullptr_t
  • Typ zmiennoprzecinkowy (od C++20)
  • Wskaźnik lub odwołanie do obiektu
  • Wskaźnik lub odwołanie do funkcji
  • Wskaźnik lub odwołanie do funkcji składowej
  • Dosłowny typ klasy (od C++20)

Pierwszy przykład parametru szablonu innego niż typ zobaczyliśmy, gdy omówione std::bitset w lekcji O.1 — Flagi bitowe i manipulacja bitami za pomocą std::bitset:

#include <bitset>

int main()
{
    std::bitset<8> bits{ 0b0000'0101 }; // The <8> is a non-type template parameter

    return 0;
}

W przypadku std::bitset, parametr szablonu typu non-type służy do określenia std::bitset ile bitów, które chcemy przechowywać.

Definiowanie naszych własnych parametrów szablonu typu non-type

Oto prosty przykład funkcji, która wykorzystuje szablon typu int parametr:

#include <iostream>

template <int N> // zadeklaruj inny parametr szablonu typu w nazwie N
void print()
{
    std::cout << N << '\n'; // użyj tutaj wartości N
}

int main()
{
    print<5>(); // 5 na nasz szablon alternatywny argument

    return 0;
}

Ten przykład wyświetla:

5

W linii 3 mamy deklarację parametrów szablonu. Wewnątrz nawiasów kątowych definiujemy parametr szablonu niebędący typem o nazwie N , który będzie symbolem zastępczym wartości typu int. Wewnątrz funkcji print() używamy wartości N.

W linii 11 mamy wywołanie funkcji print(), która używa wartości int 5 jako argumentu szablonu innego niż typ. Kiedy kompilator zobaczy to wywołanie, utworzy instancję funkcji, która wygląda mniej więcej tak:

template <>
void print<5>()
{
    std::cout << 5 << '\n';
}

W czasie wykonywania, gdy ta funkcja zostanie wywołana z main(), drukuje 5.

Następnie program się kończy. Całkiem proste, prawda?

Podobnie jak T jest zwykle używany jako nazwa pierwszego parametru szablonu typu, N jest konwencjonalnie używany jako nazwa parametru szablonu innego niż typ int.

Najlepsza praktyka

Użyj N jako nazwa parametru szablonu innego niż typ int.

Jakie są przydatne parametry szablonu innego typu for?

Począwszy od C++20, parametry funkcji nie mogą być constexpr. Dotyczy to normalnych funkcji, funkcji constexpr (co ma sens, ponieważ muszą być możliwe do uruchomienia w czasie wykonywania) i, co może być zaskakujące, nawet funkcji consteval.

Załóżmy, że mamy taką funkcję jak ta:

#include <cassert>
#include <cmath> // dla std::sqrt
#include <iostream>

double getSqrt(double d)
{
    assert(d >= 0.0 && "getSqrt(): d must be non-negative");

    // Powyższe potwierdzenie prawdopodobnie zostanie skompilowane w trybie innym niż debugowanie builds
    if (d >= 0)
        return std::sqrt(d);

    return 0.0;
}

int main()
{
    std::cout << getSqrt(5.0) << '\n';
    std::cout << getSqrt(-5.0) << '\n';

    return 0;
}

Po uruchomieniu wywołanie getSqrt(-5.0) zostanie potwierdzone w czasie wykonywania. Chociaż jest to lepsze niż nic, ponieważ -5.0 jest dosłownym (i domyślnie constexpr), byłoby lepiej, gdybyśmy mogli static_assert, aby błędy takie jak ten były wychwytywane w czasie kompilacji. Jednak static_assert wymaga stałego wyrażenia, a parametry funkcji nie mogą być constexpr…

Jeśli jednak zamiast tego zmienimy parametr funkcji na parametr szablonu niebędący typem, możemy zrobić dokładnie tak, jak chcemy:

#include <cmath> // dla std::sqrt
#include <iostream>

template <double D> // wymaga C++20 dla parametrów zmiennoprzecinkowych nietypowych
double getSqrt()
{
    static_assert(D >= 0.0, "getSqrt(): D must be non-negative");

    if constexpr (D >= 0) // zignoruj constexpr w tym przykładzie
        return std::sqrt(D); // co dziwne, std::sqrt nie jest funkcją constexpr (do C++26)

    return 0.0;
}

int main()
{
    std::cout << getSqrt<5.0>() << '\n';
    std::cout << getSqrt<-5.0>() << '\n';

    return 0;
}

Ta wersja nie zostanie skompilowana. Gdy kompilator napotka getSqrt<-5.0>(), utworzy instancję i wywoła funkcję, która wygląda mniej więcej tak:

template <>
double getSqrt<-5.0>()
{
    static_assert(-5.0 >= 0.0, "getSqrt(): D must be non-negative");

    if constexpr (-5.0 >= 0) // zignoruj constexpr w tym przykładzie
        return std::sqrt(-5.0);

    return 0.0;
}

Warunek static_assert jest fałszywy, więc kompilator potwierdza.

Kluczowa informacja

Parametry szablonu niebędące typem są używane głównie wtedy, gdy musimy przekazać wartości constexpr do funkcji (lub typów klas), aby można było ich używać w kontekstach wymagających stałego wyrażenia.

Klasa type std::bitset używa parametru szablonu innego niż typ do zdefiniowania liczby bitów do przechowywania, ponieważ liczba bitów musi być wartością constexpr.

Nota autora

Konieczność używania parametrów szablonu innych niż typ w celu obejścia ograniczenia mówiącego, że parametry funkcji nie mogą być constexpr, nie jest świetna. Ocenia się kilka różnych propozycji, które mogą pomóc w rozwiązaniu takich sytuacji. Spodziewam się, że możemy zobaczyć lepsze rozwiązanie tego problemu w przyszłym standardzie języka C++.

Niejawne konwersje dla argumentów szablonu niebędących typem Opcjonalne

Niektóre argumenty szablonu niebędące typem mogą być niejawnie konwertowane w celu dopasowania parametrów szablonu innego typu niż inny typ. Na przykład:

#include <iostream>

template <int N> // int parametr inny niż typ szablonu
void print()
{
    std::cout << N << '\n';
}

int main()
{
    print<5>();   // nie jest konieczna konwersja
    print<'c'>(); // „c” skonwertowane na typ int, wypisuje 99

    return 0;
}

Wypisuje:

5
99

W powyższym przykładzie 'c' jest konwertowany na int w celu dopasowania parametru szablonu innego niż typ szablonu funkcji print(), który następnie drukuje wartość jako int.

W tym kontekście dozwolone są tylko niektóre typy konwersji constexpr. Do najpowszechniejszych typów dozwolonych konwersji należą:

  • Promocje integralne (np. char Do int)
  • Konwersje integralne (np. char Do long lub int Do char)
  • konwersje zdefiniowane przez użytkownika (np. pewna klasa zdefiniowana przez program na int)
  • lwartość na rwartość (np. jakaś zmienna x do wartości x)

Należy pamiętać, że ta lista jest mniej liberalna niż typ niejawne konwersje dozwolone przy inicjalizacji listy. Na przykład możesz zainicjalizować zmienną typu double użycie constexpr int, ale constexpr int argument szablonu niebędący typem nie zostanie przekonwertowany na double parametr szablonu innego typu.

Pełną listę dozwolonych konwersji można znaleźć tutaj w podsekcji „Przekształcone wyrażenie stałe”.

W przeciwieństwie do w przypadku normalnych funkcji algorytm dopasowywania wywołań szablonów funkcji do definicji szablonów funkcji nie jest skomplikowany, a niektóre dopasowania nie są traktowane priorytetowo w stosunku do innych w oparciu o typ wymaganej konwersji (lub jej brak). Oznacza to, że jeśli szablon funkcji jest przeciążony różnymi rodzajami parametrów szablonu niebędących typem, może to bardzo łatwo skutkować niejednoznacznym dopasowaniem:

#include <iostream>

template <int N> // int parametr inny niż typ szablonu
void print()
{
    std::cout << N << '\n';
}

template <char N> // char nietypowy parametr szablonu
void print()
{
    std::cout << N << '\n';
}

int main()
{
    print<5>();   // nie wyjątkowe ograniczenia z int N = 5 i char N = 5
    print<'c'>(); // nie jednorazowe ograniczenia z int N = 99 i char N = 'c'

    return 0;
}

Być może, co zaskakujące, oba te wywołania print() dadzą niejednoznaczny wynik. dopasowania.

Dedukcja typu dla parametrów szablonu innego typu przy użyciu auto C++17

Począwszy od C++17, parametry szablonu innego typu mogą używać auto aby kompilator wydedukował parametr szablonu innego typu na podstawie argumentu szablonu:

#include <iostream>

template <auto N> // wydedukuj parametr szablonu niebędący typem z argumentu szablonu
void print()
{
    std::cout << N << '\n';
}

int main()
{
    print<5>();   // N wydany jako int `5`
    print<'c'>(); // N jest wyprowadzane jako znak „c”.

    return 0;
}

To kompiluje i daje oczekiwany wynik:

5
c

Dla zaawansowanych czytelników

Możesz się zastanawiać, dlaczego ten przykład nie daje niejednoznacznego dopasowania, takiego jak przykład z poprzedniej sekcji. Kompilator najpierw szuka niejednoznacznych dopasowań, a następnie tworzy instancję szablonu funkcji, jeśli nie ma żadnych niejednoznacznych dopasowań. W tym przypadku istnieje tylko jeden szablon funkcji, więc nie ma możliwości niejednoznaczności.

Po utworzeniu szablonu funkcji dla powyższego przykładu program wygląda mniej więcej tak:

#include <iostream>

template <auto N>
void print()
{
    std::cout << N << '\n';
}

template <>
void print<5>() // zauważ, że to jest print<5>, a nie print<int>
{
    std::cout << 5 << '\n';
}

template <>
void print<'c'>() // zauważ, że to jest print<`c`>, a nie print<char>
{
    std::cout << 'c' << '\n';
}

int main()
{
    print<5>();   // wywołuje drukowanie<5>
    print<'c'>(); // wywołuje print<'c'>

    return 0;
}

Czas quizu

Pytanie nr 1

Napisz szablon funkcji constexpr z parametrem szablonu innym niż typ, który zwraca silnię argument szablonu. Poniższy program nie powinien się skompilować, gdy osiągnie factorial<-3>().

// zdefiniuj tutaj szablon funkcji silni()

int main()
{
    static_assert(factorial<0>() == 1);
    static_assert(factorial<3>() == 6);
    static_assert(factorial<5>() == 120);

    factorial<-3>(); // nie powinna się kompilować

    return 0;
}

Pokaż rozwiązanie

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