Szablony wariadyczne
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 67 z 104.
A co, jeśli potrzebujesz funkcji, która przyjmuje dowolną liczbę argumentów dowolnych typów? Tradycyjne szablony wymagają określenia dokładnej liczby potrzebnych parametrów typów. Szablony wariadyczne rozwiązują ten problem, umożliwiając szablonom przyjmowanie dowolnej liczby argumentów szablonu.
Składnia używa ... (wielokropka), aby utworzyć pakiet parametrów:
template <typename... Args>
void printAll(Args... args) {
// Args to pakiet parametrów szablonu
// args to pakiet parametrów funkcji
}Aby przetwarzać argumenty, zazwyczaj używa się rekurencji z przypadkiem bazowym. Pakiet parametrów rozwija się po jednym argumencie, aż nie pozostanie żaden:
// Przypadek bazowy: nie pozostały żadne argumenty
void print() {
std::cout << std::endl;
}
// Przypadek rekurencyjny: przetwórz pierwszy argument, wywołaj funkcję rekurencyjnie dla pozostałych
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // Rozwiń pozostałe argumenty
}
print(1, 3.14, "hello", 'x'); // Wynik: 1 3.14 hello xMożesz również używać wyrażeń składanych (C++17) do prostszych operacji bez jawnej rekurencji:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // Wyrażenie fold: dodaje wszystkie argumenty
}
std::cout << sum(1, 2, 3, 4) << std::endl; // Wynik: 10Szablony wariadyczne stanowią podstawę wielu funkcji biblioteki standardowej, takich jak std::make_unique, std::tuple i std::function. Umożliwiają tworzenie bezpiecznych pod względem typów funkcji, które działają z dowolną kombinacją typów i liczby argumentów.
Wyzwanie
ŁatwyZbudujmy elastyczny system logowania, który wykorzystuje szablony wariadyczne do obsługi komunikatów z dowolną liczbą argumentów. Utworzysz narzędzia, które mogą łączyć wartości, zliczać argumenty i drukować sformatowany wynik — wszystko z użyciem pakietów parametrów.
Rozmieścisz swój kod w dwóch plikach:
Variadic.h: Zdefiniuj tutaj swoje funkcje szablonów wariadycznych.Utwórz funkcję
printAll, która drukuje wszystkie argumenty oddzielone spacjami, a na końcu znak nowej linii. Zastosuj podejście rekurencyjne z przypadkiem bazowym, który drukuje tylko znak nowej linii, oraz przypadkiem rekurencyjnym, który drukuje pierwszy argument, spację, a następnie wywołuje rekurencję dla pozostałych argumentów.Utwórz funkcję
countArgs, która zwraca liczbę przekazanych do niej argumentów. Użyjsizeof..., aby uzyskać rozmiar pakietu parametrów.Utwórz funkcję
sum, która dodaje wszystkie argumenty liczbowe i zwraca wynik. Użyj wyrażenia zwijającego z operatorem+.Utwórz funkcję
product, która mnoży wszystkie argumenty liczbowe i zwraca wynik. Użyj wyrażenia zwijającego z operatorem*.main.cpp: Odczytaj cztery dane wejściowe (każdą w osobnym wierszu):- Liczba całkowita
- Liczba typu double
- Ciąg znaków
- Znak
Zademonstruj działanie swoich szablonów wariadycznych, wykonując następujące czynności:
- Wywołaj
printAllze wszystkimi czterema danymi wejściowymi i wydrukuj wynik - Wydrukuj liczbę argumentów, wywołując
countArgsz liczbą całkowitą, liczbą typu double i znakiem:Argument count: <count> - Oblicz sumę liczby całkowitej, liczby typu double oraz wartości 10 i 5, a następnie wydrukuj:
Sum: <result> - Oblicz iloczyn liczby całkowitej oraz wartości 2 i 3, a następnie wydrukuj:
Product: <result> - Wywołaj
printAlltylko z ciągiem znaków (jeden argument) - Wywołaj
printAllbez argumentów (powinien wydrukować tylko znak nowej linii)
Na przykład dla danych wejściowych 5, 2.5, Hello i X:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
Zwróć uwagę, jak printAll obsługuje dowolną liczbę argumentów różnych typów — od czterech argumentów aż do zera. Funkcje sum i product używają wyrażeń zwijających, aby elegancko łączyć wszystkie wartości bez jawnej rekurencji. Funkcja countArgs pokazuje, jak sizeof... pozwala uzyskać rozmiar pakietu w czasie kompilacji.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: Wywołaj printAll ze wszystkimi czterema danymi wejściowymi
// TODO: Wypisz liczbę argumentów, używając countArgs z intVal, doubleVal i charVal
// Format: "Argument count: <count>"
// TODO: Oblicz i wypisz sumę intVal, doubleVal, 10 i 5
// Format: "Sum: <result>"
// TODO: Oblicz i wypisz iloczyn intVal, 2 i 3
// Format: "Product: <result>"
// TODO: Wywołaj printAll tylko z ciągiem znaków
// TODO: Wywołaj printAll bez argumentów
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online