Зачем мне явно писать ключевое слово "auto"?

Я двигаюсь к C++ 11 из C++ 98 и знаком с ключевым словом auto. Мне было интересно, почему нам нужно явно объявлять auto если компилятор способен автоматически выводить тип. Я знаю, что C++ - это строго типизированный язык, и это правило, но не удалось ли достичь такого же результата без явного объявления переменной auto?

Ответ 1

Удаление явного auto может сломать язык:

например

int main()
{
    int n;
    {
        auto n = 0; // this shadows the outer n.
    }
}

где вы можете увидеть, что сбросив auto не будет затенять внешний n.

Ответ 2

Ваш вопрос допускает две интерпретации:

  • Зачем нам "авто" вообще? Разве мы не можем просто отказаться от него?
  • Почему мы обязаны использовать авто? Разве мы не можем иметь это неявное, если оно не дано?

Вирсаба хорошо ответила первой интерпретацией, во-вторых, рассмотрим следующее (предполагая, что пока никаких других заявлений не существует, гипотетически действительный C++):

int f();
double g();

n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double

if(n == d)
{
    n = 7; // reassigns n
    auto d = 2.0; // new d, shadowing the outer one
}

Возможно, другие языки уйдут вполне хорошо (ну, может быть, помимо вопроса о тени)... Однако это не так C++, и вопрос (в смысле второй интерпретации) теперь: Зачем?

На этот раз ответ не так очевиден, как в первой интерпретации. Ясно одно: явное требование для ключевого слова делает язык более безопасным (я не знаю, вызвало ли это решение языкового комитета, но оно остается точкой):

grummel = f();

// ...

if(true)
{
    brummel = f();
  //^ uh, oh, a typo...
}

Можем ли мы договориться об этом, не требуя дальнейших объяснений?

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

(цитируемый комментарий psmears из-за его важности - спасибо за намек)

Ответ 3

не удалось ли достичь такого же результата без явного объявления переменной auto?

Я немного перефразирую ваш вопрос таким образом, чтобы помочь вам понять, почему вам нужен auto:

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

Разве это не возможно? Конечно, это было "возможно". Вопрос в том, стоит ли делать это.

Большинство синтаксисов на других языках, которые не являются именами типов, работают одним из двух способов. Там Go-like way, где name := value; объявляет переменную. И там Python-подобный путь, где name = value; объявляет новую переменную, если name ранее не было объявлено.

Предположим, что нет синтаксических проблем с применением синтаксиса к C++ (хотя я уже вижу этот identifier за которым следуют : в C++ означает "сделать ярлык"). Итак, что вы теряете по сравнению с заполнителями?

Ну, я больше не могу этого делать:

auto &name = get<0>(some_tuple);

См., auto всегда означает "значение". Если вы хотите получить ссылку, вам нужно явно использовать &. И он по праву не сможет скомпилировать, если выражение присваивания является prvalue. Ни один из синтаксисов, основанных на назначении, не имеет возможности различать ссылки и значения.

Теперь вы можете сделать такие синтаксисы присваивания выводами, если заданное значение является ссылкой. Но это означает, что вы не можете этого сделать:

auto name = get<0>(some_tuple);

Это копирует из кортежа, создавая объект, не зависящий от some_tuple. Иногда это именно то, что вы хотите. Это еще более полезно, если вы хотите перейти из кортежа с помощью auto name = get<0>(std::move(some_tuple)); ,

Хорошо, возможно, мы могли бы немного расширить эти синтаксисы, чтобы учесть это различие. Возможно &name := value; или &name = value; означало бы вывод ссылки, такой как auto&.

Хорошо. Как насчет этого:

decltype(auto) name = some_thing();

О, это правильно; C++ на самом деле имеет два заполнителя: auto и decltype(auto). Основная идея этого вывода состоит в том, что он работает точно так, как если бы вы сделали decltype(expr) name = expr; , Итак, в нашем случае, если some_thing() является объектом, он выведет объект. Если some_thing() является ссылкой, он будет выводить ссылку.

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

Итак, теперь нам нужно добавить больше к нашему синтаксису. name ::= value; означает "делать то, что decltype(auto) ". У меня нет эквивалента для варианта Питонов.

Глядя на этот синтаксис, разве это не так легко случайно ввести в заблуждение? Мало того, это вряд ли самодокументируется. Даже если вы никогда не видели decltype(auto) раньше, это достаточно и достаточно очевидно, что вы можете хотя бы легко сказать, что там что-то особенное происходит. В то время как визуальная разница между ::= и := минимальна.

Но это мнение; есть более существенные проблемы. Смотрите, все это основано на использовании синтаксиса присваивания. Ну... а как насчет мест, где нельзя использовать синтаксис назначения? Как это:

for(auto &x : container)

Можем ли мы это изменить for(&x := container)? Потому что, кажется, говорит что - то очень отличается от диапазона на основе for. Похоже на то выражение инициализатора от обычного for цикла, а не диапазон на основе for. Это также был бы другой синтаксис из невыделенных случаев.

Кроме того, copy-initialization (using =) не является тем же самым в C++ как прямая инициализация (с использованием синтаксиса конструктора). Итак, name := value; может не работать в случаях, когда было бы auto name(value).

Конечно, вы могли бы заявить, что := будет использовать прямую инициализацию, но это было бы совершенно несовместимо с тем, как ведет себя остальная часть C++.

Кроме того, есть еще одна вещь: C++ 14. Это дало нам одну полезную функцию вычета: вывод типа возврата. Но это основано на заполнителях. Подобно диапазону, основанный for принципе, он основан на имени типа, которое заполняется компилятором, а не каким-то синтаксисом, применяемым к определенному имени и выражению.

Смотрите, все эти проблемы исходят из одного источника: вы изобретаете совершенно новый синтаксис для объявления переменных. Декларации, основанные на заполнителях, не должны были придумывать новый синтаксис. Они используют тот же синтаксис, что и раньше; они просто используют новое ключевое слово, которое действует как тип, но имеет особое значение. Это то, что позволяет ему работать в зависимости for диапазона и для вывода типа возврата. Это позволяет ему иметь несколько форм (auto vs. decltype(auto)). И так далее.

Заполнители работают, потому что они являются самым простым решением проблемы, одновременно сохраняя все преимущества и общность использования фактического имени типа. Если вы придумали другую альтернативу, которая работала бы так же универсально, как и заполнители, маловероятно, чтобы это было так же просто, как заполнители.

Если это не было просто написание заметок с разными ключевыми словами или символами...

Ответ 4

Короче говоря, auto можно было бы отбросить в некоторых случаях, но это приведет к несогласованности.

Прежде всего, как указано, синтаксис объявления в C++ есть <type> <varname>. Явные декларации требуют определенного типа или, по крайней мере, ключевого слова объявления на своем месте. Таким образом, мы могли бы использовать var <varname> или declare <varname> или что-то в этом роде, но auto - ключевое слово long в C++ и является хорошим кандидатом для ключевого слова с автоматическим типом вывода.

Можно ли неявно объявлять переменные по заданию, не нарушая все?

Иногда да. Вы не можете выполнять назначение внешних функций, поэтому вы можете использовать синтаксис присваивания для объявлений там. Но такой подход привел бы к несогласованности с языком, что, возможно, привело бы к человеческим ошибкам.

a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
  return 0;
}

И когда дело доходит до любых локальных переменных, явные декларации являются способом управления областью переменной.

a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here

Если допускается неоднозначный синтаксис, объявление глобальных переменных может внезапно преобразовывать локальные неявные объявления в присваивания. Поиск этих преобразований потребует проверки всего. И чтобы избежать столкновений, вам понадобятся уникальные имена для всех глобалов, которые разрушают всю идею обзора. Так что это действительно плохо.

Ответ 5

auto - это ключевое слово, которое вы можете использовать в тех местах, где вам обычно нужно указывать тип.

  int x = some_function();

Можно сделать более общим, сделав автоматический тип int:

  auto x = some_function();

Так что это консервативное расширение языка; он вписывается в существующий синтаксис. Без него x = some_function() становится оператором присваивания, больше не является объявлением.

Ответ 6

синтаксис должен быть однозначным, а также обратно совместимым.

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

auto n = 0; // fine
n=0; // statememt, n is undefined.

Ответ 7

Добавляя к предыдущим ответам, еще одно примечание от старого пердуна: похоже, вы можете видеть это как преимущество, чтобы иметь возможность просто начать использовать новую переменную, не объявляя ее каким-либо образом.

В языках с возможностью неявного определения переменных это может быть большой проблемой, особенно в более крупных системах. Вы делаете одну опечатку, и вы отлаживаете часами только, чтобы узнать, что вы непреднамеренно ввели переменную со значением нуля (или хуже) - blue vs bleu, label vs lable... в результате вы не можете действительно доверять любому коду без тщательная проверка точных имен переменных.

Просто использование auto сообщает компилятору и сопровождающему, что вы намерены объявить новую переменную.

Подумайте об этом, чтобы избежать подобных ночных кошмаров. В FORTRAN было введено выражение "неявное отсутствие", и вы видите, что оно используется во всех серьезных программах FORTRAN в наши дни. Не иметь это просто... страшно.