Wielokropki i szablony wariadyczne

W tym artykule pokazano, jak używać wielokropka (...) z szablonami Variadic języka C++. Wielokropek miał wiele zastosowań w językach C i C++. Obejmują one listy argumentów zmiennych dla funkcji. Funkcja printf() z biblioteki środowiska uruchomieniowego języka C jest jednym z najbardziej znanych przykładów.

Szablon wariadyczny to szablon klasy lub funkcji, który obsługuje dowolną liczbę argumentów. Ten mechanizm jest szczególnie przydatny dla deweloperów bibliotek języka C++: można zastosować go zarówno do szablonów klas, jak i szablonów funkcji, a tym samym zapewnić szeroką gamę funkcji bezpiecznych i nietrygalnych oraz elastyczności.

Składnia

Wielokropek jest używany na dwa sposoby przez szablony wariadyczne. Po lewej stronie nazwy parametru oznacza pakiet parametrów, a po prawej stronie nazwy parametru rozszerza pakiety parametrów na osobne nazwy.

Oto podstawowy przykład składni definicji szablonu klasy variadic:

template<typename... Arguments> class classname;

W przypadku obu pakietów parametrów i rozszerzeń można dodać białe znaki wokół wielokropka, zgodnie z preferencjami, jak pokazano w tym przykładzie:

template<typename ...Arguments> class classname;

Lub w tym przykładzie:

template<typename ... Arguments> class classname;

W tym artykule użyto konwencji pokazanej w pierwszym przykładzie (wielokropek jest dołączony do typenameelementu ).

W poprzednich przykładach Arguments jest pakietem parametrów. Klasa classname może akceptować zmienną liczbę argumentów, jak w poniższych 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 szablonu klasy wariadycznej, można również wymagać co najmniej jednego parametru:

template <typename First, typename... Rest> class classname;

Oto podstawowy przykład składni szablonu funkcji wariadycznej:

template <typename... Arguments> returntype functionname(Arguments... args);

Pakiet Arguments parametrów jest następnie rozszerzany do użycia, jak pokazano w następnej sekcji.

Możliwe są inne formy składni szablonu funkcji wariadycznej, w tym, ale nie tylko, następujące przykłady:

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);

Podobnie jak w przypadku wariadycznych definicji klas szablonów, można tworzyć funkcje, które wymagają co najmniej jednego parametru:

template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);

Szablony Variadic używają sizeof...() operatora (niepowiązanego ze starszym sizeof() operatorem):

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

Więcej informacji o umieszczaniu wielokropka

Wcześniej w tym artykule opisano umieszczanie wielokropka, które definiuje pakiety parametrów i rozszerzenia w tej formie: "po lewej stronie nazwy parametru oznacza pakiet parametrów, a po prawej stronie nazwy parametru rozszerza pakiety parametrów na osobne nazwy". Chociaż technicznie prawda, może to być mylące w tłumaczeniu na kod. Rozważ następujące kwestie:

  • W pliku template-parameter-list (template <parameter-list>) typename... wprowadzono pakiet parametrów szablonu.

  • W klauzuli-deklaracji parametru (func(parameter-list)) wielokropek "najwyższego poziomu" wprowadza pakiet parametrów funkcji, a pozycjonowanie wielokropka jest ważne:

    // 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);
    
  • Gdzie wielokropek pojawia się natychmiast po nazwie parametru, masz rozszerzenie pakietu parametrów.

Przykład

Dobrym sposobem zilustrowania mechanizmu szablonu funkcji wariadycznej jest użycie go w przepisaniu niektórych funkcji programu 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ą szablony funkcji wariadycznych, używa rekursji jakiejś formy, ale nieco różni się od tradycyjnej rekursji. Tradycyjna rekursja polega na wywołaniu samej funkcji przy użyciu tego samego podpisu. (Może być przeciążony lub szablonowy, ale ten sam podpis jest wybierany za każdym razem). Rekursja wariadyczna polega na wywołaniu szablonu funkcji wariadzkiej przy użyciu różnych (prawie zawsze malejących) liczb argumentów, a tym samym oznaczania innego podpisu za każdym razem. Nadal jest wymagany "przypadek podstawowy", ale charakter rekursji jest inny.