Wielokropki i szablony wariadyczne
W tym artykule przedstawiono sposób użycia wielokropka (...) z szablonami języka C++ ze zmienną liczbą parametrów.Wielokropek miał wiele zastosowań w C i C++.Należą do nich listy zmiennych argumentów dla funkcji.Funkcja printf() z biblioteki środowiska uruchomieniowego języka C jest jednym z najbardziej znanych przykładów.
Szablon zmiennych jest szablonem klasy lub funkcji, która obsługuje dowolną liczbę argumentów.Mechanizm ten jest szczególnie przydatny dla deweloperów bibliotek C++, ponieważ można go stosować zarówno do szablonów klasy jak szablonów funkcji, i tym samym zapewniać szeroki wybór bezpiecznej dla typu i zaawansowanej funkcjonalności i elastyczności.
Składnia
Wielokropek jest używany na dwa sposoby przez zmienne szablony.Na lewo od nazwy parametru, oznacza pakiet parametru, a na prawo od nazwy parametru, rozszerza pakiety parametru w osobne nazwy.
Oto przykład podstawowej składni definicji klasy zmiennego szablonu:
template<typename... Arguments> class classname;
Dla obu parametrów pakietów i rozszerzeń można dodać odstępy wokół wielokropka, na podstawie preferencji użytkownika, jak pokazano w tych przykładach:
template<typename ...Arguments> class classname;
Czy to:
template<typename ... Arguments> class classname;
Należy zauważyć, że ten artykuł używa konwencji, który jest pokazana w pierwszym przykładzie (wielokropek jest dołączony do typename).
W poprzednich przykładach Arguments jest pakietem parametrów.Klasa classname może akceptować zmienną liczbę argumentów, jak w następujących przykładach:
template<typename... Arguments> class vtclass;
vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;
Korzystając z definicji klasy zmiennej szablonu, można także wymagać co najmniej jednego parametru:
template <typename First, typename... Rest> class classname;
Oto przykład podstawowej składni funkcji zmiennego szablonu:
template <typename... Arguments> returntype functionname(Arguments... args);
Pakiet parametru Arguments jest rozszerzany do użycia, jak pokazano w następnej sekcji, Zrozumienie szablonów zmiennych.
Możliwe są inne formy składnia funkcji zmiennych szablonów — w jak w tych przykładach:
template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);
Specyfikatory takie jak const są również dozwolone:
template <typename... Arguments> returntype functionname(const Arguments&... args);
Zgodnie z definicjami klasy zmiennej szablonu, można także wykonać funkcje, które wymagają co najmniej jednego parametru:
template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);
Szablony zmienne korzystają z operatora sizeof...() (niezwiązany ze starszym operatorem sizeof()):
template<typename... Arguments>
void tfunc(const Arguments&... args)
{
const unsigned numargs = sizeof...(Arguments);
X xobj[numargs]; // array of some previously defined type X
helper_func(xobj, args...);
}
Więcej informacji na temat rozmieszczenia wielokropka
W opisanych wcześniej artykułach, położenie wielokropka, który definiuje parametr pakietów i rozszerzeń, jako "na lewo od nazwy parametru, oznacza paczkę parametr i po prawej stronie nazwy parametru, rozszerza paczkę parametru do osobnych nazw".Technicznie jest to prawdą, ale może być mylące w tłumaczeniu do kodu.Należy rozważyć:
W template-parameter-list (template <parameter-list>), typename... wprowadzono paczkę parametru szablonu.
W parameter-declaration-clause (func(parameter-list)), wielokropka "najwyższego poziomu" wprowadza paczkę parametru funkcji i ważne jest pozycjonowanie wielokropka:
// v1 is NOT a function parameter pack: template <typename... Types> void func1(std::vector<Types...> v1); // v2 IS a function parameter pack: template <typename... Types> void func2(std::vector<Types>... v2);
W przypadku gdy wielokropek pojawia się natychmiast po nazwie parametru, użytkownik ma rozszerzenie paczki parametru.
Przykład
Dobrym sposobem ilustrowania mechanizmu funkcji zmiennych szablonu jest skorzystanie z niego w celu ponownego napisania niektórych funkcji printf:
#include <iostream>
using namespace std;
void print() {
cout << endl;
}
template <typename T> void print(const T& t) {
cout << t << endl;
}
template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
cout << first << ", ";
print(rest...); // recursive call using pack expansion syntax
}
int main()
{
print(); // calls first overload, outputting only a newline
print(1); // calls second overload
// these call the third overload, the variadic template,
// which uses recursion as needed.
print(10, 20);
print(100, 200, 300);
print("first", 2, "third", 3.14159);
}
Dane wyjściowe
1
10, 20
100, 200, 300
first, 2, third, 3.14159
[!UWAGA]
Większość implementacji, które zawierają funkcje szablonów zmiennych, używa jakiejś formy rekursji, ale różni się ona nieco od tradycyjnych rekursji. Tradycyjna rekursja obejmuje wywoływanie funkcji przez nią samą za pomocą tego samego podpisu. (Może być przeciążone lub oparte na szablonach, ale ten sam podpis jest wybierany każdorazowo). Rekursja zmienna polega na wywołaniu szablonu funkcji zmiennej za pomocą różnej ilości (prawie zawsze malejących) argumentów, a tym samym wybijaniu za każdym razem innego podpisu.„Przypadek podstawowy” jest wciąż wymagany, ale jest to inny rodzaj rekursji.