Пример шаблонов Variadic

Рассмотрим следующий код, я не понимаю, почему должна быть определена пустая функция печати. ​​

#include <iostream>
using namespace std;

void print()
{   
}   

    template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args)
{   
    cout << firstArg << endl; // print first argument
    print(args...); // call print() for remaining arguments
}

int main()
{   
    int i=1;
    int  j=2;
    char c = 'T';
    print(i,"hello",j,i,c,"word");

}   

Ответ 1

ПРАВИЛЬНЫЙ ПУТЬ:

Вариадические шаблоны строго связаны с induction, математическим понятием.

Компилятор разрешает следующий вызов функции

print('a', 3, 4.0f);

в

std::cout<< 'a' <<std::endl;
print(3, 4.0f);

который разрешен в

std::cout<< 'a' <<std::endl;
std::cout<< 3 <<std::endl;
print( 4.0f);

который разрешен в

std::cout<< 'a' <<std::endl;
std::cout<< 3 <<std::endl;
std::cout<< 4.0f <<std::endl;
print();

В этот момент он ищет функцию перегрузки, совпадение которой является пустой функцией.

  • Все функции, имеющие 1 или более аргументов, сопоставляются с вариационным шаблоном
  • Все функции, не имеющие аргументов, сопоставляются с пустой функцией

Преступник состоит в том, что для каждой возможной комбинации параметров у вас должно быть только 1 функция.


ОШИБКА 1:

Выполнение следующей ошибки будет

template< typename T>
void print( const T& arg) // first version
{   
    cout<< arg<<endl;
}   

template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args) // second version
{   
    cout << firstArg << endl; // print first argument
    print(args...); // call print() for remaining arguments
}

Потому что, когда вы вызываете print, компилятор не знает, какую функцию вызывать.

Использует ли print(3) "первая" или "вторая" версия? Оба будут действительны, поскольку первый имеет 1 параметр, а второй может принимать и один параметр.

print(3); // error, ambiguous, which one you want to call, the 1st or the 2nd?

ОШИБКА 2:

В любом случае, это будет ошибка

// No empty function

template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args) 
{   
    cout << firstArg << endl; // print first argument
    print(args...); // call print() for remaining arguments
}

На самом деле, если вы используете его в одиночку без компилятора,

 print('k', 0, 6.5);

который разрешен в

 std::cout<<'k'<<std::endl;
 print(0, 6.5);

который разрешен в

 std::cout<<'k'<<std::endl;
 std::cout<< 0 <<std::endl;
 print( 6.5);

который разрешен в

 std::cout<<'k'<<std::endl;
 std::cout<< 0 <<std::endl;
 std::cout<< 6.5 <<std::endl;
 print(); //Oops error, no function 'print' to call with no arguments

Как вы видите в последней попытке, компилятор пытается вызвать print() без аргументов. Однако, если такая функция не существует, она не вызывается и почему вы должны предоставить эту пустую функцию (не беспокойтесь, компилятор будет оптимизировать код, поэтому пустые функции не уменьшат производительность).

Ответ 2

Если у вас нет пустой функции print, представьте вызов с двумя параметрами:

  • print (a, b) = > cout < a < endl и call print (b)
  • print (b) = > cout < < b < endl и call print()

oups, print() не существует, потому что существует только print с хотя бы одним параметром! Поэтому вам нужно print без параметров.

print без каких-либо параметров - ваш последний вызов

Ответ 3

Поскольку (используя очень "простой" уровень объяснения) механизм вариационных шаблонов работает как рекурсия (рекурсия НЕ, но это самый простой способ понять он), который "потребляет" список переменных параметров, поэтому вам нужно будет определить функцию " остановить", при которой она будет повторяться на последнем этапе рекурсии, когда список "потребляемых" параметров " пустой". Это было объяснение того, что мне было легче понять это довольно сложное понятие.

На первом шаге (в main) вы получите функцию, которая имеет параметры: (int, const char*, int, int, char, const char*)

Затем переменные параметры медленно обрабатываются в самой вариационной функции, оставляя вас на втором шаге с (const char*, int, int, char, const char*), затем (int, int, char, const char*) и так далее... до тех пор, пока вы не достигнете последнего элемента (const char*), и когда это также обрабатывается на следующем шаге, в конце которого вы получаете (), а компилятор нуждается в этой функции в качестве "терминатора"

(Да, это очень нетехнический и звучит как дедушка-лягушка, рассказывающая историю маленьким лягушкам...)

Ответ 4

Чтобы добавить к другим ответам, я хотел бы показать, что компилятор должен генерировать для вызовов шаблонов.

nm -g -C ./a.out (не оптимизированная сборка) дает:

void print<char [5]>(char const (&) [5])
void print<char [5]>(char const (&) [5])
void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<char, char [5]>(char const&, char const (&) [5])
void print<char, char [5]>(char const&, char const (&) [5])
void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
print()

Вы можете увидеть все экземпляры функции print. Конечной функцией, которая в конечном итоге вызывает print(), является void print<char [5]>(char const (&) [5])>

Вы можете видеть, что при передаче пустого пакета параметров список параметров шаблона обязательно должен быть пустым. Поэтому он просто вызывает print(). Если вы явно указали параметры шаблона, например print<T, Args...>(t, args...), вы получите бесконечную рекурсию.

Ответ 5

Рекурсия - это самый общий способ программирования вариационных шаблонов, но это далеко не единственный способ. Для простых случаев, подобных этому, выполнение расширения пакета непосредственно в расширенном списке инициализаторов короче и, скорее всего, быстрее компилируется.

template <typename... Types>
void print (const Types&... args)
{   
    using expander = int[];
    (void) expander { 0, (void(cout << args << endl), 0) ...};
}

В С++ 17 мы сможем использовать выражение fold:

template <typename... Types>
void print (const Types&... args)
{   
    (void(cout << args << endl) , ...);
}