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&
", ваш код может не работать так же хорошо или даже работать некорректно.
В целом, современный консенсус состоит в том, что вывод типов, как правило, безопасно использовать для объектов, и что это может помочь сделать ваш код более читаемым за счет уменьшения акцента на информации о типе, в результате чего логика вашего кода будет выделяться лучше.
Лучшая практика
Используйте вывод типа для переменных, если вам не нужно фиксировать конкретный тип.
Примечание автора
В будущих уроках, когда нам покажется, что отображение информации о типах полезно для понимания концепции или примера, мы продолжим использовать явные типы вместо вывода типов.