Подтвердить что ты не робот

Использование ключевого слова 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. Похоже, что использование функции шаблона является важной частью здесь, факторинг цикла из шаблона не приводит к ошибкам. Итак, больше похоже на ошибку в компиляторе?

Живая демонстрация - минимальный код

4b9b3361

Ответ 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 являются разными типами, компилятор генерирует ошибку.