Использование ключевого слова auto С++ 11 для объявления двух (или более) переменных
У меня такой код:
template<class ListItem>
static void printList(QList<ListItem>* list)
{
for (auto i = list->size() - 1, j = -1; i >= 0; --i) {
std::cout << i << ", " << j << ": " << list->at(i) << std::endl;
}
}
Когда я скомпилирую его с помощью g++ 6.2.1, я получаю следующий вывод компилятора:
test.cpp: In function ‘void printList(QList<T>*)’:
test.cpp:10:7: error: inconsistent deduction for ‘auto’: ‘auto’ and then ‘int’
for (auto i = list->size() - 1, j = -1; i >= 0; --i) {
^~~~
Я бы это понял, если переменные имели разные типы, такие как auto i = 0.0, j = 0;
, но в этом случае список является указателем на QList, а метод size() возвращает int
, -1
сам по себе должен быть int
, тоже. Сообщение об ошибке также немного странно.
Переменные i
и j
нужны только в этом цикле, и я хотел бы объявить их как параметры цикла. Не печатать int
вместо auto, но я хотел бы знать: is auto
не предполагается использовать для объявления нескольких переменных за один раз, или я чего-то здесь не вижу, и это действительно ошибочный код, или, возможно, это ошибка компилятора?
P.S. Похоже, что использование функции шаблона является важной частью здесь, факторинг цикла из шаблона не приводит к ошибкам. Итак, больше похоже на ошибку в компиляторе?
Живая демонстрация - минимальный код
Ответы
Ответ 1
Это ошибка в GCC.
Согласно [dcl.spec.auto]/1:
Типовые параметры auto
и decltype(auto)
используются для обозначения тип заполнителя, который будет заменен позже путем вычитания из инициализатор. [...]
Правила вывода аргумента шаблона никогда не выводят тип auto
. Цель дедукции в этом случае состоит в том, чтобы заменить auto
на выведенный тип.
В этом примере list
имеет зависимый тип (он зависит от параметра шаблона ListItem
), поэтому выражение list->size() - 1
также имеет зависимый тип, что делает тип i
также зависимым, что означает, что он будет разрешен только после создания шаблона функции printList
. Только тогда можно проверить другие семантические ограничения, связанные с этим объявлением.
Согласно [temp.res]/8:
Знание имен имен имен типов позволяет использовать синтаксис каждого шаблона быть проверенным. Программа плохо сформирована, не требуется диагностика, если:
[... длинный список случаев, к которым здесь не применяется...]
В противном случае для шаблона должна быть выдана диагностика, для которой действительная специализация может быть сгенерирована. [Примечание: если шаблон экземпляры, ошибки будут диагностированы в соответствии с другими правилами в этот стандарт. Точно, когда эти ошибки диагностируются, это качество осуществление проблема. - конечная нота]
(акцент мой)
GCC ошибочно выдает эту ошибку при анализе определения шаблона printList
, так как может быть сгенерирована достоверная специализация шаблона. На самом деле, если QList
не имеет специализаций, для которых size()
возвращает что-то еще, чем int
, декларация для i
и j
будет действительна во всех экземплярах printList
.
Все цитаты из N4606, (почти) текущий рабочий проект, но соответствующие части вышеприведенных цитат не изменено с С++ 14.
Обновление: Подтверждено как регрессия в GCC 6/7. Благодаря TC для отчета об ошибке.
Обновление: Исходная ошибка (78693) была исправлена для предстоящих выпусков 6.4 и 7.0. Он также обнаружил некоторые другие проблемы, связанные с тем, как GCC обрабатывает такие конструкции, в результате чего появляются еще два отчета об ошибках: 79009 и 79013.
Ответ 2
Как упоминалось в моем комментарии к вашему ответу, я согласен с представленным вами анализом.
Простейшая форма проблемы (демонстрация):
template<class T>
void foo (T t) {
auto i = t, j = 1; // error: inconsistent deduction for ‘auto’: ‘auto’ and then ‘int’
}
int main () {}
В случае шаблонов компилятор на первом этапе проверяет базовый синтаксис, не создавая его. В нашем случае мы никогда не призываем foo()
.
Теперь в приведенном выше примере decltype(auto)
для i
все еще auto
, потому что зависимый тип T
неизвестен. Однако j
, безусловно, int
.
Следовательно, ошибка компилятора имеет смысл. Настоящее поведение (g++ >= 6) может быть или не быть ошибкой. Это зависит от того, что мы ожидаем от компилятора.: -)
Однако эту ошибку нельзя осуждать. Ниже приведена стандартная цитата из проект С++ 17:
7.1.7.4.1 Вычет типа залогодержателя
4 Если заполнитель является спецификатором автоматического типа, выводимый тип T, заменяющий T, определяется с использованием правил вывода аргумента шаблона. Получите P из T, заменив вхождения auto либо новым изобретенным тип шаблона U
То же самое присутствует в С++ 14, как 7.1.6.4/7.
Почему эта ошибка сообщается в первой проверке шаблона?
Мы можем по праву утверждать, что компилятор настолько "педантичен" в первой проверке синтаксиса. Поскольку мы не создаем экземпляр, тогда не должно быть хорошо! Даже если мы создадим экземпляр, не следует ли давать ошибку только для проблемных вызовов!
Что делает g++ - 5. Почему они потрудились изменить его?
Я думаю, это правильный аргумент. С g++ - 5, если я звоню:
foo(1); // ok
foo(1.0); // error reported inside `foo()`, referencing this line
Затем компилятор правильно сообщает об ошибке и ее иерархии, когда i
и j
имеют разные типы.
Ответ 3
Затем я буду обрабатывать информацию, полученную по этой теме.
Проблема в примере кода заключается в использовании функции шаблона.
Компилятор выполняет общую проверку шаблона сначала, не создавая его, это означает, что типы, которые являются аргументами шаблона (и типами, которые зависят от них, как и другие шаблоны), неизвестны, а auto
, если он зависит от этих неизвестных типов, снова выводится в auto
(или не выводится в какой-то конкретный тип). Мне никогда не казалось, что даже после вычета auto
может быть auto
. Теперь исходный текст ошибки компилятора имеет смысл: переменная j
выводится типа int
, но переменная i
по-прежнему auto
после вычета. Поскольку auto
и int
являются разными типами, компилятор генерирует ошибку.