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

A 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> // declare a non-type template parameter of type int named N
void print()
{
    std::cout << N << '\n'; // use value of N here
}

int main()
{
    print<5>(); // 5 is our non-type template 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> // for std::sqrt
#include <iostream>

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

    // The assert above will probably be compiled out in non-debug 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> // for std::sqrt
#include <iostream>

template <double D> // requires C++20 for floating point non-type parameters
double getSqrt()
{
    static_assert(D >= 0.0, "getSqrt(): D must be non-negative");

    if constexpr (D >= 0) // ignore the constexpr here for this example
        return std::sqrt(D); // strangely, std::sqrt isn't a constexpr function (until 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) // ignore the constexpr here for this example
        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 non-type template parameter
void print()
{
    std::cout << N << '\n';
}

int main()
{
    print<5>();   // no conversion necessary
    print<'c'>(); // 'c' converted to type int, prints 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 non-type template parameter
void print()
{
    std::cout << N << '\n';
}

template <char N> // char non-type template parameter
void print()
{
    std::cout << N << '\n';
}

int main()
{
    print<5>();   // ambiguous match with int N = 5 and char N = 5
    print<'c'>(); // ambiguous match with int N = 99 and 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> // deduce non-type template parameter from template argument
void print()
{
    std::cout << N << '\n';
}

int main()
{
    print<5>();   // N deduced as int `5`
    print<'c'>(); // N deduced as char `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>() // note that this is print<5> and not print<int>
{
    std::cout << 5 << '\n';
}

template <>
void print<'c'>() // note that this is print<`c`> and not print<char>
{
    std::cout << 'c' << '\n';
}

int main()
{
    print<5>();   // calls print<5>
    print<'c'>(); // calls 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>().

// define your factorial() function template here

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

    factorial<-3>(); // should fail to compile

    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