8.7 – Вывод типов для объектов с использованием ключевого слова auto
В этом простом определении переменной скрывается небольшая избыточность:
double d{ 5.0 };
Поскольку C++ – язык со строгой типизацией, от нас требуется указывать явный тип для всех объектов. Таким образом, мы указали, что переменная d имеет тип double.
Однако литеральное значение 5.0, используемое для инициализации d, также имеет тип double (неявно определяется через формат литерала).
В случаях, когда мы хотим, чтобы переменная и ее инициализатор имели один и тот же тип, мы фактически предоставляем информацию одного и того же типа дважды.
Вывод типа для инициализированных переменных
Вывод типа (или англоязычные термины type inference и type deduction) – это функция, которая позволяет компилятору определять тип объекта из его инициализатора. Чтобы использовать вывод типа, вместо типа переменной используется ключевое слово auto:
int main()
{
auto d{ 5.0 }; // 5.0 - это литерал double, поэтому d будет иметь тип double
auto i{ 1 + 2 }; // 1 + 2 вычисляется как int, поэтому i будет типа int
auto x { i }; // i - это int, поэтому x тоже будет типа int
return 0;
}
В первом случае, поскольку 5.0 – это литерал double, компилятор определит, что переменная d должна иметь тип double. Во втором случае выражение 1 + 2 дает результат int, поэтому переменная i будет иметь тип int. В третьем случае ранее предполагалось, что i имеет тип int, поэтому x также будет выведен как имеющий тип int.
Поскольку вызовы функций являются допустимыми выражениями, мы можем использовать вывод типа, даже когда наш инициализатор является вызовом функции:
int add(int x, int y)
{
return x + y;
}
int main()
{
auto sum { add(5, 6) }; // add() возвращает int, поэтому тип sum будет выведен как int
return 0;
}
Функция add() возвращает значение типа int, поэтому компилятор определит, что переменная sum должна иметь тип int.
Вывод типа не будет работать для объектов, у которых нет инициализаторов, или у которых пустые инициализаторы. Таким образом, следующее недопустимо:
int main()
{
auto x; // Компилятор не может определить тип x
auto y{ }; // Компилятор не может определить тип y
return 0;
}
Хотя использование auto вместо базовых типов данных позволяет сэкономить лишь несколько нажатий клавиш (если экономия вообще будет), в будущих уроках мы увидим примеры, когда типы становятся сложными и длинными (а в некоторых случаях их может быть трудно понять). В таких случаях использование auto может сэкономить много времени при вводе текста (и избежать опечаток).
Вывод типа отбрасывает квалификаторы const
В большинстве случаев вывод типа удаляет квалификатор const из выводимых типов. Например:
int main()
{
const int x { 5 }; // x имеет тип const int
auto y { x }; // y будет типа int (const отбрасывается)
}
В приведенном выше примере x имеет тип const int, но при выводе типа для переменной y с использованием x в качестве инициализатора вывод типа выводит тип как int, а не const int.
Если вы хотите, чтобы выведенный тип был константным, вы можете использовать ключевое слово const вместе с ключевым словом auto:
int main()
{
const int x { 5 }; // x имеет тип const int
const auto y { x }; // y будет типа const int
}
В этом примере тип, выведенный из x, будет int (константность отброшена), но поскольку мы добавили квалификатор const во время определения переменной y, переменная y будет const int.
Для продвинутых читателей
Вывод типа не удаляет квалификатор const для указателей на константные значения, такие как типы, выведенные из строковых литералов в стиле C.
Выведение типа отбрасывает ссылки
Для продвинутых читателей
Мы еще не рассмотрели ссылки, но определение типа также отбрасывает ссылки.
Например, если вы используете вывод типа с инициализатором типа int&, выводимым типом будет int, а не int&.
int x{ 5 }; // x - обычный int
int& y{ x }; // y - ссылка int&
auto z{ y }; // z будет "int", а не "int&", потому что ссылки отбрасываются
Вы можете сделать так, чтобы выведенный тип был ссылочным, используя auto& вместо auto.
int x{ 5 }; // x - обычный int
auto& y{ x }; // выведенный тип - "int", но мы предоставили &,
// поэтому y будет ссылкой "int&"
Вы также можете вывести константную ссылку, используя const auto&
Преимущества и недостатки вывода типа
Вывод типа не только удобен, но и имеет ряд других преимуществ.
Во-первых, если две или более переменных определены в соседних строках, имена переменных будут выровнены, что поможет повысить удобочитаемость:
// труднее читать
int a { 5 };
double b { 6.7 };
// легче читать
auto c { 5 };
auto d { 6.7 };
Во-вторых, вывод типа работает только с переменными, у которых есть инициализаторы, поэтому, если вы привыкли использовать вывод типа, он может помочь избежать непреднамеренно неинициализированных переменных:
int x; // упс, мы забыли инициализировать x, но компилятор может не пожаловаться
auto y; // компилятор выдаст ошибку, потому что он не может определить тип для y
В-третьих, вам гарантировано отсутствие непреднамеренных преобразований, влияющих на производительность:
// плохо: неявно преобразует 5 из int в double
double x { 5 };
// хорошо: y - это число int (надеюсь, это то, что вы хотели),
// и преобразования не происходит
auto y { 5 };
У вывода типа также есть несколько недостатков.
Во-первых, вывод типа скрывает информацию о типе объекта в коде. Хотя хорошая IDE должна иметь возможность показать вам выведенный тип (например, при наведении курсора на переменную), всё же при использовании вывода типа немного легче делать ошибки, связанные с типами.
Например:
auto y { 5 }; // ой, нам здесь нужен double, но мы случайно предоставили литерал int
В приведенном выше коде, если бы мы явно указали y как типа double, y был бы double, даже если мы случайно предоставили инициализатор литералом int. При выводе типа y будет иметь тип int.
Вот еще один пример:
#include <iostream>
int main()
{
auto x { 3 };
auto y { 2 };
std::cout << x / y; // упс, мы хотели здесь деление с плавающей запятой
return 0;
}
В этом примере менее очевидно, что мы получаем целочисленное деление, а не деление с плавающей запятой.
Во-вторых, если тип инициализатора изменится, тип переменной, использующей вывод типа, также изменится, возможно, неожиданно. Рассмотрим код:
auto sum { add(5, 6) + gravity };
Если тип возвращаемого значения add изменится с int на double, или gravity изменится с int на double, sum также изменит тип с int на double.
Для продвинутых читателей
В-третьих, поскольку при выводе типов ссылки отбрасываются, если вы используете "auto" вместо "auto&", ваш код может не работать так же хорошо или даже работать некорректно.
В целом, современный консенсус состоит в том, что вывод типов, как правило, безопасно использовать для объектов, и что это может помочь сделать ваш код более читаемым за счет уменьшения акцента на информации о типе, в результате чего логика вашего кода будет выделяться лучше.
Лучшая практика
Используйте вывод типа для переменных, если вам не нужно фиксировать конкретный тип.
Примечание автора
В будущих уроках, когда нам покажется, что отображение информации о типах полезно для понимания концепции или примера, мы продолжим использовать явные типы вместо вывода типов.
