Неявные преобразования
Неявные преобразования выполняются всякий раз, когда выражение некоторого типа T1
используется в контексте, который не принимает этот тип, но принимает какой-либо другой T2
; в частности:
- когда выражение используется в качестве аргумента при вызове функции, объявленной с
T2
в качестве параметра; - когда выражение используется в качестве операнда с оператором, который ожидает
T2
; - при инициализации нового объекта типа
T2
, включая операторreturn
в функции, возвращающейT2
; - когда выражение используется в операторе
switch
(T2
имеет целочисленный тип); - когда выражение используется в операторе
if
или цикле (T2
равно bool).
Программа корректно сформирована (компилируется) только в том случае, если существует одна однозначная неявная последовательность преобразования из T1
в T2
.
Если имеется несколько перегрузок вызываемой функции или оператора, после построения последовательности неявного преобразования из T1
в каждый доступный T2
, правила разрешения перегрузки решают, какая перегрузка компилируется.
Примечание: в арифметических выражениях тип назначения для неявных преобразований операндов в бинарных операторах определяется отдельным набором правил: обычные арифметические преобразования
Содержание |
[править] Порядок преобразований
Последовательность неявного преобразования состоит из следующего в указанном порядке:
При рассмотрении аргумента конструктора или определяемой пользователем функции преобразования допускается только стандартная последовательность преобразования (в противном случае определяемые пользователем преобразования могут быть эффективно объединены в цепочку). При преобразовании из одного неклассового типа в другой неклассовый тип допускается только стандартная последовательность преобразования.
Стандартная последовательность преобразования состоит из следующего, в этом порядке:
3) ноль или одно преобразование указателя на функцию;
|
(начиная с C++17) |
Определяемое пользователем преобразование состоит из нуля или одного неявного вызова преобразующего конструктора с одним аргументом или функции преобразования
Говорят, что выражение e
неявно преобразуется в T2
тогда и только тогда, когда T2
может быть инициализирован копированием из e
, то есть объявление T2 t = e; корректно сформировано (компилируется), для некоторого придуманного временного t
. Обратите внимание, что это отличается от прямой инициализации (T2 t(e)), где дополнительно учитываются явные конструкторы и функции преобразования.
[править] Контекстные преобразования
В следующих контекстах ожидается тип bool, и выполняется неявное преобразование, если объявление bool t(e); правильно сформировано (то есть рассматривается функция явного преобразования, такая как
|
(начиная с C++11) |
В следующих контекстах ожидается контекстно-зависимый тип T
, а выражение e
типа класса E
допускается только в том случае, если E
имеет одну неявную определяемую пользователем функцию преобразования в допустимый тип (до C++14)среди допустимых типов есть ровно один тип T
, такой что E
имеет неявные функции преобразования, возвращаемые типы которых (возможно, cv-квалифицированные) T
или ссылка на T
(возможно, cv-квалифицированная), а e
неявно преобразуется в T
(начиная с C++14). Такое выражение e
называется контекстно неявно преобразованным к указанному типу T
. Обратите внимание, что явные функции преобразования не учитываются, даже если они учитываются при контекстном преобразовании в bool. (начиная с C++11)
- аргумент выражения delete (
T
любой тип указателя на объект); - целочисленное константное выражение, где используется литеральный класс (
T
любое целое число или тип перечисления без области видимости (начиная с C++11), выбранная определяемая пользователем функция преобразования должна быть constexpr); - управляющее выражение оператора
switch
(T
любой целочисленный тип или тип перечисления).
#include <cassert> template<typename T> class zero_init { T val; public: zero_init() : val(static_cast<T>(0)) {} zero_init(T val) : val(val) {} operator T&() { return val; } operator T() const { return val; } }; int main() { zero_init<int> i; assert(i == 0); i = 7; assert(i == 7); switch (i) {} // ошибка до C++14 (более одной функции преобразования) // OK начиная с C++14 (обе функции преобразуют в один // и тот же тип int) switch (i + 0) {} // всегда в порядке (неявное преобразование) }
[править] Преобразования значений
Преобразования значений это преобразования, которые изменяют категорию значений выражения. Они имеют место всякий раз, когда выражение появляется как операнд оператора, который ожидает выражения другой категории значений.
[править] Преобразование lvalue в rvalue
glvalue любого типа, не являющегося функцией или массивом T
, может быть неявно преобразовано в prvalue тогоже типа. Если T
не относится к классовому типу, это преобразование также удаляет cv-квалификаторы.
Объект, обозначенный glvalue, не доступен, если:
- преобразование происходит в неоценённом контексте, таком как операнд sizeof, noexcept, decltype, (начиная с C++11) или статической формы typeid
|
(начиная с C++11) |
- значение, хранящееся в объекте, является константой времени компиляции, и выполняются некоторые другие условия (смотрите Использование ODR)
Если T
не относится к классовому типу, значение, содержащееся в объекте, создаётся как результат prvalue. Для типа класса это преобразование
эффективно конструирует копированием временный объект типа |
(до C++17) |
преобразует glvalue в prvalue, объект результата которого инициализируется копированием с помощью glvalue. |
(начиная с C++17) |
Это преобразование моделирует акт чтения значения из ячейки памяти в регистр ЦП.
Если объект, на который ссылается glvalue, содержит неопределённое значение (например, полученное инициализацией по умолчанию автоматической переменной, неклассового типа), поведение неопределено за исключением случаев, когда неопределённое значение имеет, возможно, cv-квалифицированный тип unsigned char или std::byte (начиная с C++17).
Поведение также определяется реализацией (а не неопределено), если glvalue содержит значение указателя, которое недействительно.
[править] Преобразование массива в указатель
lvalue или rvalue типа "массив из N
T
" или "массив неизвестной границы из T
" может быть неявно преобразовано в prvalue типа "указатель на T
". Если массив является значением prvalue, происходит временная материализация. (начиная с C++17) Результирующий указатель ссылается на первый элемент массива (для подробностей смотрите распад массива в указатель)
Временная материализацияprvalue любого полного типа struct S { int m; }; int i = S().m; // доступ к элементам ожидает значение glvalue начиная с C++17; // S() prvalue преобразуется в xvalue Временная материализация происходит в следующих ситуациях:
Обратите внимание, что временная материализация не происходит при инициализации объекта из значения prvalue того же типа (путём прямой инициализации или инициализации копированием): такой объект инициализируется непосредственно из инициализатора. Это обеспечивает "гарантированный пропуск копирования". |
(начиная с C++17) |
[править] Функция в указатель
lvalue типа функции T
можно неявно преобразовать в prvalue указатель на эту функцию. Это не относится к нестатическим функциям-элементам, поскольку lvalue, ссылающиеся на нестатические функции-элементы, не существуют.
[править] Числовые продвижения
[править] Целочисленное продвижение
prvalue небольших целочисленных типов (например, char) могут быть преобразованы в prvalue более крупных целочисленных типов (например, int). В частности, арифметические операторы не принимают в качестве аргументов типы меньше int, а целочисленные продвижения автоматически применяются после преобразования lvalue в rvalue, если это применимо. Это преобразование всегда сохраняет значение.
Следующие неявные преобразования классифицируются как целочисленные продвижения:
-
signed char
илиsigned short
можно преобразовать в int; -
unsigned char
илиunsigned short
можно преобразовать в int, если он может содержать весь диапазон значений, и unsigned int в противном случае; -
char
можно преобразовать в int или unsigned int в зависимости от базового типа: signed char или unsigned char (смотрите выше); -
wchar_t
,char8_t
(начиная с C++20),char16_t
иchar32_t
(начиная с C++11) могут быть преобразованы в первый тип из следующего списка, способного содержать весь их диапазон значений: int, unsigned int, long, unsigned long, long long, unsigned long long (начиная с C++11); - тип перечисления с незаданной областью видимости (начиная с C++11), базовый тип которого не является фиксированным, может быть преобразован в первый тип из следующего списка, который может содержать весь диапазон значений: int, unsigned int, long, unsigned long, long long или unsigned long long, расширенные целые типы с более высоким рангом преобразования (в порядке ранжирования, знаковый имеет предпочтение перед беззнаковым) (начиная с C++11). Если диапазон значений больше, целочисленные преобразования не применяются;
- тип перечисления с незаданной областью видимости (начиная с C++11), базовый тип которого является фиксированным, может быть преобразован в его базовый тип и, если базовый тип также подлежит целочисленному продвижению, в продвинутый базовый тип. Преобразование в не продвигаемый базовый тип лучше для разрешения перегрузки;
- тип битового поля может быть преобразован в int, если он может представлять весь диапазон значений битового поля, иначе в unsigned int, если он может представлять весь диапазон значений битового поля, в противном случае целочисленные продвижения не применяются;
- тип bool можно преобразовать в int, при этом значение false становится 0 а true становится 1.
-
Обратите внимание, что все остальные преобразования не являются продвижениями; например, разрешение перегрузки выбирает char -> int (продвижение) вместо char -> short (преобразование).
[править] Продвижение с плавающей запятой
prvalue типа float можно преобразовать в prvalue типа double. Значение не меняется.
[править] Числовые преобразования
В отличие от продвижений, числовые преобразования могут изменить значения с потенциальной потерей точности.
[править] Целочисленные преобразования
prvalue целочисленного типа или типа перечисления без области видимости (начиная с C++11) может быть преобразовано в любой другой целочисленный тип. Если преобразование указано в разделе целочисленных продвижений, это продвижение, а не преобразование.
- Если целевой тип беззнаковый, результирующее значение является наименьшим беззнаковым значением, равным исходному значению по модулю 2n
, где n количество битов, используемых для представления типа назначения.
- То есть, в зависимости от того, является ли тип назначения более широким или более узким, целые числа со знаком расширяются по знаку[сноска 1] или усечением, а целые числа без знака дополняются нулём или усекаются соответственно.
- Если целевой тип знаковый, значение не изменяется, если исходное целое число может быть представлено в целевом типе. В противном случае результат определяется реализацией (до C++20)это уникальное значение целевого типа, равное исходному значению по модулю 2n
, где n это количество битов, используемых для представления целевого типа. (начиная с C++20). (Обратите внимание, что это отличается от целочисленного арифметического переполнения со знаком, которое не определено). - Если исходным типом является bool, значение false преобразуется в ноль, а значение true преобразуется в значение один целевого типа (обратите внимание, что если тип назначения int, это целочисленное продвижение, а не целочисленное преобразование).
- Если целевой тип bool, это логическое преобразование (смотрите ниже).
- Если целевой тип беззнаковый, результирующее значение является наименьшим беззнаковым значением, равным исходному значению по модулю 2n
[править] Преобразования с плавающей запятой
prvalue типа с плавающей запятой можно преобразовать в prvalue любого другого типа с плавающей запятой. Если преобразование указано в продвижениях с плавающей запятой, это продвижение, а не преобразование.
- Если исходное значение может быть точно представлено в целевом типе, оно не изменяется.
- Если исходное значение находится между двумя представляемыми значениями целевого типа, результатом является одно из этих двух значений (определяется реализацией, какое из двух, но, если поддерживается арифметика IEEE, округление по умолчанию до ближайшего).
- В противном случае поведение не определено.
[править] Преобразования плавающей запятой в целое
- prvalue типа с плавающей запятой можно преобразовать в prvalue любого целочисленного типа. Дробная часть усекается, то есть отбрасывается. Если значение не может уместиться в целевом типе, поведение не определено (даже если целевой тип беззнаковый, арифметика по модулю не применяется). Если целевой тип bool, это логическое преобразование (смотрите ниже).
- Значение prvalue целого числа или типа перечисления с незаданной областью видимости (начиная с C++11) можно преобразовать в значение prvalue любого типа с плавающей запятой. Результат точен, насколько это возможно. Если значение может уместиться в целевом типе, но не может быть представлено точно, реализация определяет, будет ли выбрано ближайшее более высокое или ближайшее более низкое представляемое значение, но, если поддерживается арифметика IEEE, округление по умолчанию до ближайшего. Если значение не умещается в целевом типе, поведение не определено. Если исходный тип bool, значение false преобразуется в ноль, а значение true преобразуется в единицу.
[править] Преобразования указателя
- Константа нулевого указателя (смотрите NULL), может быть преобразована в любой тип указателя, и результатом будет значение нулевого указателя этого типа. Такое преобразование (известное как преобразование нулевого указателя) разрешено для преобразования в cv-квалифицированный тип как одно преобразование, то есть оно не считается комбинацией числовых и квалификационных преобразований.
- Указатель prvalue на любой (необязательно cv-квалифицированный) объектный тип
T
может быть преобразован в указатель prvalue на (cv-квалифицированный) void. Результирующий указатель представляет то же место в памяти, что и исходное значение указателя. Если исходный указатель является нулевым значением указателя, результатом является значение нулевого указателя целевого типа. - Указатель prvalue на тип производного класса (необязательно cv-квалифицированного) может быть преобразован в указатель prvalue на его базовый класс (cv-квалифицированный). Если базовый класс недоступен или неоднозначен, преобразование имеет неправильный формат (не будет компилироваться). Результатом преобразования является указатель на подобъект базового класса в указанном объекте. Значение нулевого указателя преобразуется в значение нулевого указателя целевого типа.
[править] Преобразования указателя на элемент
- Константа нулевого указателя (смотрите NULL) может быть преобразована в любой тип указателя на элемент, и результатом будет значение нулевого указателя на элемент этого типа. Такое преобразование (известное как преобразование пустого указателя на элемент) может преобразовывать в cv-квалифицированный тип как одно преобразование, то есть оно не считается комбинацией числовых и квалификационных преобразований.
- Указатель prvalue на элемент некоторого типа
T
в базовом классеB
может быть преобразован в prvalue указатель на элемент того же типаT
в его производном классеD
. ЕслиB
недоступен, неоднозначен или является виртуальным базовым дляD
или является базовым для некоторого промежуточного виртуального базового дляD
, преобразование некорректно (не компилируется). Результирующий указатель может быть разыменован с помощью объектаD
, и он будет обращаться к элементу внутри базового подобъектаB
этого объектаD
. Значение нулевого указателя преобразуется в значение нулевого указателя целевого типа.
[править] Логические преобразования
prvalue типы целого, с плавающей запятой, перечисления без области видисмоти (начиная с C++11), указателя и указателя на элемент могут быть преобразованы в prvalue типа bool.
Нулевое значение (для целого, с плавающей запятой и перечисления без области видимости (начиная с C++11)) и нулевой указатель и значения нулевого указателя на элемент становятся false. Все остальные значения становятся true.
В контексте прямой инициализации объект bool может быть инициализирован значением prvalue типа std::nullptr_t, включая nullptr. Результирующее значение равно false. Однако это не считается неявным преобразованием. |
(начиная с C++11) |
[править] Квалификационные преобразования
- Указатель типа prvalue на cv-квалифицированный тип
T
может быть преобразован в указатель prvalue на более точную cv-квалификацию того же типаT
(другими словами, можно добавить const и volatile). - Значение prvalue типа указателя на элемент cv-квалифицированного типа
T
в классеX
может быть преобразовано в prvalue-указатель на элемент более точно cv-квалифицированного типаT
в классеX
.
- Указатель типа prvalue на cv-квалифицированный тип
"Более точно" cv-квалифицированного означает, что
- указатель на неквалифицированный тип может быть преобразован в указатель на
const
; - указатель на неквалифицированный тип может быть преобразован в указатель на
volatile
; - указатель на неквалифицированный тип может быть преобразован в указатель на
const volatile
; - указатель на тип
const
может быть преобразован в указатель наconst volatile
; - указатель на тип
volatile
может быть преобразован в указатель наconst volatile
.
- указатель на неквалифицированный тип может быть преобразован в указатель на
Для многоуровневых указателей применяются следующие ограничения: многоуровневый указатель P1
, который является cv1
0-квалифицированным указателем на cv1
1-квалифицированный указатель на ... cv1
n-1-квалифицированный указатель на cv1
n-квалифицированный T
можно преобразовать в многоуровневый указатель P2
, который является cv2
0-квалифицированным указателем на cv2
1-квалифицированный указатель на ... cv2
n-1-квалифицированный указатель на cv2
n-квалифицированный T
только если
- количество уровней
n
одинаково для обоих указателей;
- количество уровней
|
(начиная с C++20) |
- если есть const в квалификации cv1
k на каком-то уровне (кроме нулевого)P1
, есть const на том же уровне cv2
kP2
; - если есть volatile в квалификации cv1
k на каком-то уровне (кроме нулевого)P1
, есть volatile на том же уровне cv2
kP2
;
- если есть const в квалификации cv1
|
(начиная с C++20) |
- если на каком-то уровне
k
P2
является более точно cv-квалифицированным, чемP1
или есть тип массива с известной границей вP1
и тип массива с неизвестной границей вP2
(начиная с C++20), тогда должен быть const на каждом отдельном уровень (кроме нулевого уровня)P2
до k: cv2
1, cv2
2 ... cv2
k. - одни и те же правила применяются к многоуровневым указателям на элементы и многоуровневым смешанным указателям на объекты и указатели на элементы;
- те же правила применяются к многоуровневым указателям, которые включают указатели на массив известных или неизвестных границ на любом уровне (массивы cv-квалифицированных элементов сами считаются идентично cv-квалифицированными);
- к нулевому уровню применяются правила преобразования немногоуровневых квалификаций.
- если на каком-то уровне
char** p = 0; const char** p1 = p; // ошибка: уровень 2 более точно cv-квалифицирован, // но уровень 1 не является const const char* const * p2 = p; // OK: уровень 2 более точно cv-квалифицирован // и const добавлено на уровень 1 volatile char * const * p3 = p; // OK: уровень 2 более точно cv-квалифицирован и const // добавлено на уровень 1 volatile const char* const* p4 = p2; // OK: уровень 2 более точно cv-квалифицирован и // const уже существует на уровне 1 double *a[2][3]; double const * const (*ap)[3] = a; // OK double * const (*ap1)[] = a; // OK, начиная с C++20
Обратите внимание, что в языке программирования C, const/volatile можно добавить только к первому уровню:
char** p = 0; char * const* p1 = p; // OK в C и C++ const char* const * p2 = p; // ошибка в C, OK в C++
Преобразования указателя на функцию
void (*p)(); void (**pp)() noexcept = &p; // ошибка: невозможно преобразовать в указатель // на функцию noexcept struct S { typedef void (*p)(); operator p(); }; void (*q)() noexcept = S(); // ошибка: невозможно преобразовать в указатель // на функцию noexcept |
(начиная с C++17) |
[править] Проблема безопасного bool
До введения явных функций преобразования в C++11 разработка класса, который можно было бы использовать в логических контекстах (например, if(obj) { ... }) представляла собой проблему: при наличии определяемой пользователем функции преобразования, такой как T::operator bool() const;, неявная последовательность преобразования допускала одну дополнительную стандартную последовательность преобразования после вызова этой функции, что означает, что результирующий bool мог быть преобразован в int, допуская такой код, как obj << 1; or int i = obj;.
Одно раннее решение для этого можно увидеть в std::basic_ios, который определяет operator! и operator void*(до C++11), чтобы такой код, как if(std::cin) {...} компилировался, поскольку void* преобразуется в bool, но int n = std::cout; не компилируется, потому что void* нельзя преобразовать в int. Это по-прежнему позволяет компилировать бессмысленный код, такой как delete std::cout;, и многие сторонние библиотеки до C++11 были разработаны с использованием более сложного решения, известного как Идиома Безопасного Bool (Safe Bool).
Явное преобразование bool также можно использовать для решения проблемы безопасного bool explicit operator bool() const { ... } |
(начиная с C++11) |
[править] Сноски
- ↑ Это применимо только в том случае, если арифметика представляет собой дополнение до двух, которое требуется только для целочисленных типов точной ширины. Обратите внимание, однако, что на данный момент все платформы с компилятором C++ используют арифметику с дополнением до двух
[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 172 | C++98 | тип перечисления продвигается на основе его базового типа | вместо этого на основе его диапазона значений |
CWG 330 | C++98 | преобразование из double * const (*p)[3] в double const * const (*p)[3] недействительно |
преобразование действительно |
CWG 519 | C++98 | не гарантировалось сохранение нулевых значений указателя при преобразовании в другой тип указателя |
всегда сохраняется |
CWG 616 | C++98 | поведение преобразования lvalue в rvalue любого неинициализированного объекта и объектов указателей недопустимых значений всегда было неопределённым |
разрешён неопределённый unsigned char; использование недопустимых указателей определяется реализацией |
CWG 685 | C++98 | базовый тип типа перечисления не имел приоритета в целочисленном продвижении, если он фиксирован |
приоритетный |
CWG 707 | C++98 | преобразование целых чисел в числа с плавающей запятой имело определённое поведение во всех случаях |
поведение не определено, если преобразуемое значение выходит за пределы целевого диапазона |
CWG 1423 | C++11 | std::nullptr_t преобразуется в bool как при прямой, так и при копирующей инициализации |
только прямая инициализация |
CWG 1781 | C++11 | std::nullptr_t в bool считается неявным преобразованием, даже если оно допустимо только для прямой инициализации |
больше не считается неявным преобразованием |
CWG 1787 | C++98 | поведение чтения из неопределённого unsigned char, кэшированного в регистре, было неопределённым |
сделано чётко определённым |
CWG 2484 | C++20 | char8_t и char16_t имеют разные целочисленные стратегии продвижения, но они подходят обоим |
char8_t следует продвигать так же, как char16_t |
[править] Смотрите также
-
const_cast
-
static_cast
-
dynamic_cast
-
reinterpret_cast
- explicit cast
- определяемое пользователем преобразование
Документация C по Неявные преобразования
|