#cpp #шаблоны_с++
templatevoid declare_and_call(Args... args) { // declaration ReturnType bar(Args...); // (1) OK ReturnType bar(args...); // (2) error: variable or field ‘bar’ declared void // function call bar(args...); } // another .cpp file // definition void bar() { } int main() { declare_and_call (); } Почему второе объявление не является объявлением функции при пустой пачке параметров, а воспринимается как объявление переменной?
Ответы
Ответ 1
Решение о том, что означает строчка 2, принимается еще на этапе синтаксического анализа, еще при парсинге определения шаблона, еще до того, как будут рассматриваться какие-то специализации шаблона и переданные вами шаблонные аргументы. Парсер языка видит в строчке 2 ReturnType bar(args...); А args... - это pack expansion, примененный к function parameter pack. Этот вариант ни в коем случае не является объявлением функции уже на синтаксическом уровне: в грамматике языка С++ нет правил вывода, который бы приводили к тому, чтобы список параметров функции состоял из function parameter pack expansion. Template parameter pack expansion - разрешается, function parameter pack expansion - не разрешается. Вот и все. Решение о том, что второе объявление НЕ является объявлением функции было принято сразу и зафиксировано еще до того, как шаблон начал специализироваться. Поэтому не имеет никакого значения, сколько аргументов и какие аргументы вы передаете в шаблон declare_and_call. Обратите внимание, кстати, что именно за счет этого parameter packs дают нам возможность сделать то, что раньше было невозможным - использовать инициализатор () в объявлении объекта, не боясь при этом получить объявление функции templatevoid foo(T... t) { int i(t...); // Если расширение пусто, то переменная `i` получает значение `0` } Ответ 2
Второй вариант не приводит объявлению функции, потому что args... разворачивается не в список типов, а в список значений. Это приводит к тому, что объявляется переменная типа ReturnType с именем bar и инициализируется args... А так как имя bar строкой выше отдано во власть функции, возникает ошибка переопределения имени для новой сущности. В качестве примера я немного изменил ваш код, убрав объявление функции, чтобы видеть, что второй bar - это переменная: #include#include template void declare_and_call(Args... args) { // declaration //ReturnType bar(Args...); // (1) OK ReturnType bar(args...); // (2) error: variable or field ‘bar’ declared void std::cout << std::is_same_v << "\n"; std::cout << bar << "\n"; } int main() { declare_and_call (42.5); } Вывод: 1 42 1 говорит о том тип bar соответствует ReturnType 42 - о том, что 42.5 (вещественное), переданное как аргумент double преобразовалось при инициализации в целое 42. В случае отсутствия параметров args... судя по всему выраждается в {}. Это надо бы уточнить, но пока не готов ковырять стандарт. А при ReturnType void получается вовсе ошибка инстанцирования, т.к. переменная не может быть void.
Комментариев нет:
Отправить комментарий