Страницы

Поиск по вопросам

пятница, 20 декабря 2019 г.

Локальное объявление функции с пустой пачкой аргументов

#cpp #шаблоны_с++


template
void 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 дают нам возможность сделать то, что раньше было невозможным - использовать инициализатор () в объявлении объекта, не боясь при этом получить объявление функции template void 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.

Комментариев нет:

Отправить комментарий