Неявные преобразования
Неявные преобразования выполняются всякий раз, когда выражение некоторого типа T1 используется в контексте, который не принимает этот тип, но принимает какой-либо другой тип T2; в частности:
- когда выражение используется в качестве аргумента при вызове функции, объявленной с параметром типа
T2; - когда выражение используется в качестве операнда с оператором, который ожидает тип
T2; - при инициализации нового объекта типа
T2, включая операторreturnв функции, возвращающейT2; - когда выражение используется в операторе switch (
T2— целочисленный тип); - когда выражение используется в операторе if или цикле (
T2— тип bool).
Программа является корректной (компилируется) только в том случае, если существует однозначная последовательность неявных преобразований от T1 к T2.
Если есть несколько перегрузок вызываемой функции или оператора, после построения последовательности неявных преобразований от T1 к каждой доступной T2, правила разрешения перегрузок определяют, какая перегрузка будет скомпилирована.
Примечание: в арифметических выражениях целевой тип для неявных преобразований операндов бинарных операторов определяется отдельным набором правил: обычные арифметические преобразования.
Порядок преобразований
Последовательность неявных преобразований состоит из следующих шагов, в указанном порядке:
При рассмотрении аргумента конструктора или функции пользовательского преобразования разрешается только одно стандартное преобразование (иначе пользовательские преобразования могут быть эффективно зацеплены). При преобразовании из одного неклассового типа в другой неклассовый тип разрешается только стандартное преобразование.
Стандартное преобразование состоит из следующих шагов, в указанном порядке:
1) ноль или одно преобразование из следующего набора:- преобразование lvalue в rvalue,
- преобразование массива в указатель, и
- преобразование функции в указатель;
| 3) ноль или одно преобразование указателя на функцию; | (с C++17) |
Пользовательское преобразование состоит из ноль или одного вызова неявного одноаргументного конвертирующего конструктора или неявной функции преобразования.
Выражение e считается неявным образом преобразуемым к T2 тогда и только тогда, когда T2 может быть скопированно инициализировано из e, то есть объявление T2 t = e; является корректным (может быть скомпилировано) для некоторой вымышленной временной переменной t. Обратите внимание, что это отличается от прямой инициализации (T2 t(e)), где явные конструкторы и функции преобразования также будут учитываться.
Контекстные преобразования
| В следующих контекстах ожидается тип bool, и неявное преобразование выполняется, если объявление
| (с C++11) |
В следующих контекстах ожидается контекстно-специфический тип T, и выражение e класса E разрешается только в том случае, если
| (до C++14) |
| (с C++14) |
Такое выражение e называется контекстно неявным образом преобразованным к указанному типу T. Обратите внимание, что явные функции преобразования не учитываются, даже если они учитываются в контекстных преобразованиях в bool.(с C++11)
- аргумент оператора delete (
T— любой тип указателя на объект); - целочисленное константное выражение, где используется литеральный класс (
T— любой целочисленный или неименованный перечислимый тип, выбранная функция пользовательского преобразования должна быть 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) {} // error until C++14 (more than one conversion function)
// OK since C++14 (both functions convert to the same type int)
switch (i + 0) {} // always okay (implicit conversion)
}Преобразования значений
Преобразования значений — это преобразования, которые изменяют категорию значения выражения. Они происходят всякий раз, когда выражение появляется как операнд оператора, который ожидает выражение другой категории значения:
- Всякий раз, когда glvalue появляется как операнд оператора, который требует prvalue для данного операнда, применяются стандартные преобразования lvalue в rvalue, массив в указатель или функция в указатель, чтобы преобразовать выражение в prvalue.
| (с C++17) |
Преобразование lvalue в rvalue
An lvalue(до C++11)A glvalue(с C++11) любого нефункционального, не массивнового типа T может быть неявно преобразован в an rvalue(до C++11)a prvalue(с C++11):
- Если
Tне является классом, тип rvalue(до C++11)prvalue(с C++11) — это неквалифицированная версияT. - В противном случае, тип rvalue(до C++11)prvalue(с C++11) —
T.
Если требуется преобразование lvalue в rvalue от неполного типа, то программа некорректна.
| Когда происходит преобразование lvalue в rvalue в операнде оператора | (до C++11) |
| Когда преобразование lvalue в rvalue применяется к выражению
| (с C++11) |
| Результатом преобразования является значение, содержащееся в объекте, указанном lvalue. | (до C++11) | ||||
| Результат преобразования определяется по следующим правилам:
| (с C++11) |
Это преобразование моделирует действие чтения значения из ячейки памяти в регистр процессора.
Преобразование массива в указатель
Lvalue или rvalue типа "массив из N T" или "массив из неизвестной длины T" можно неявно преобразовать в prvalue типа "указатель на T". Если массив — prvalue, происходит создание временного объекта.(с C++17) Полученный указатель ссылается на первый элемент массива (см. разложение массива в указатель для подробностей).
Преобразование функции в указатель
Lvalue типа функции может быть неявно преобразовано в prvalue указатель на эту функцию. Это не относится к нестатическим членам функций, так как lvalue, которые ссылаются на нестатические члены функций, не существуют.
Создание временного объектаPrvalue любого полного типа struct S { int m; };
int i = S().m; // member access expects glvalue as of C++17;
// S() prvalue is converted to xvalueСоздание временного объекта происходит в следующих ситуациях:
Обратите внимание, что создание временного объекта не происходит при инициализации объекта из prvalue того же типа (прямой или копирующей инициализацией): такой объект инициализируется напрямую из инициализатора. Это обеспечивает "гарантированное удаление копирования". | (с C++17) |
Целочисленные повышения
Prvalue небольших целочисленных типов (например, char) и типов неименованных перечислений могут быть преобразованы в prvalue больших целочисленных типов (например, int). В частности, арифметические операторы не принимают типы, меньшие чем int, в качестве аргументов, и целочисленные повышения применяются автоматически после преобразования lvalue в rvalue, если применимо. Это преобразование всегда сохраняет значение.
Следующие неявные преобразования в этом разделе классифицируются как целочисленные повышения.
Целочисленные повышения из целочисленных типов
Prvalue типа bool можно преобразовать в prvalue типа int, где false становится 0 и true становится 1.
Для prvalue val целочисленного типа T за исключением bool:
val является результатом преобразования lvalue в rvalue, применённого к битовому полю, -
valможно преобразовать в prvalue типа int, если int может представить все значения битового поля; - в противном случае,
valможно преобразовать в unsigned int, если unsigned int может представить все значения битового поля; - в противном случае,
valможно преобразовать по правилам, указанным в пункте (3).
val не преобразован из битового поля), - если
Tявляется(с C++20)char16_t,char32_tили(с C++11)wchar_t,valможет быть преобразовано по правилам, указанным в пункте (3); - в противном случае, если ранг целочисленного преобразования
Tниже ранга int: -
valможно преобразовать в prvalue типа int, если int может представить все значенияT; - в противном случае,
valможно преобразовать в prvalue типа unsigned int.
unsigned int) или пункте (2) (T — один из указанных типов символов), val может быть преобразовано в prvalue первого из следующих типов, которые могут представлять все значения его базового типа: - int
- unsigned int
- long
- unsigned long
| (с C++11) |
Целочисленные повышения из типов перечислений
Значение типа перечисления без области видимости, подлежащего типу перечисления, чья базовая структура не фиксирована, может быть преобразовано к значению первого типа из следующего списка, способного хранить весь диапазон значений:
- int
- unsigned int
- long
- unsigned long
| (с C++11) |
| Значение типа перечисления без области видимости, чья базовая структура фиксирована, может быть преобразовано к его базовому типу. Более того, если базовый тип также подлежит целочисленному продвижению, к продвинутому базовому типу. Преобразование к неповышенному базовому типу предпочтительнее для целей разрешения перегрузки. | (с C++11) |
Обратите внимание, что все другие преобразования не являются продвижениями; например, разрешение перегрузки выбирает char -> int (продвижение) вместо char -> short (преобразование).
Продвижение с плавающей точкой
Значение типа float может быть преобразовано к значению типа double. Значение не изменяется.
Это преобразование называется продвижением с плавающей точкой.
Преобразования числовых типов
В отличие от продвижений, числовые преобразования могут изменять значения с возможной потерей точности.
Преобразования целых типов
Значение целого типа или типа перечисления без области видимости может быть преобразовано в любой другой целочисленный тип. Если преобразование указано в целочисленных продвижениях, это продвижение, а не преобразование.
- Если тип назначения беззнаковый, полученное значение — наименьшее беззнаковое значение, равное исходному значению по модулю 2n, где n — число битов, используемых для представления типа назначения.
- То есть, в зависимости от того, шире или уже тип назначения, целые со знаком расширяются со знаком[1] или усекаются, а беззнаковые — нулевым расширением или усечением соответственно.
- Если тип назначения со знаком, значение не изменяется, если исходное целое число может быть представлено в типе назначения. В противном случае результатом является определяемое реализацией(до C++20)уникальное значение типа назначения, равное исходному значению по модулю 2n, где n — число битов, используемых для представления типа назначения(с C++20) (обратите внимание, что это отличается от переполнения целочисленной арифметики со знаком, которое не определено).
- Если исходный тип — bool, значение
falseпреобразуется в ноль, а значениеtrueпреобразуется в значение единицы типа назначения (обратите внимание, что если тип назначения — int, это целочисленное продвижение, а не целочисленное преобразование). - Если тип назначения — bool, это преобразование в булевый тип (см. ниже).
- Это относится только в том случае, если арифметика использует дополнительный код, что требуется только для целочисленных типов с точной шириной. Однако в настоящее время все платформы с компилятором C++ используют дополнительный код.
Преобразования с плавающей точкой
| Значение типа с плавающей точкой может быть преобразовано к значению любого другого типа с плавающей точкой. | (до C++23) |
| Значение типа с плавающей точкой может быть преобразовано в значение любого другого типа с плавающей точкой с бóльшим или равным рангом преобразования типов с плавающей точкой. Значение стандартного типа с плавающей точкой может быть преобразовано в значение любого другого стандартного типа с плавающей точкой.
| (с C++23) |
Если преобразование указано в продвижениях с плавающей точкой, это продвижение, а не преобразование.
- Если исходное значение может быть представлено точно в типе назначения, оно не изменяется.
- Если исходное значение находится между двумя представимыми значениями типа назначения, результат — одно из этих двух значений (реализация определяет какое, хотя если поддерживается арифметика IEEE, округление по умолчанию — к ближайшему).
- В противном случае поведение не определено.
Преобразования между плавающей точкой и целыми типами
- Значение типа с плавающей точкой может быть преобразовано в значение любого целочисленного типа. Дробная часть усекается.
- Если усечённое значение не помещается в тип назначения, поведение не определено (даже если тип назначения беззнаковый, арифметика по модулю не применяется).
- Если тип назначения — bool, это преобразование в булевый тип (см. ниже).
- Значение целочисленного или типа перечисления без области видимости может быть преобразовано в значение любого типа с плавающей точкой. Результат точен, если возможно.
- Если значение может поместиться в тип назначения, но не может быть представлено точно, реализация определяет, будет выбрано ближайшее большее или ближайшее меньшее представимое значение, хотя если поддерживается арифметика IEEE, округление по умолчанию — к ближайшему.
- Если значение не может поместиться в тип назначения, поведение не определено.
- Если исходный тип — bool, значение
falseпреобразуется в ноль, а значениеtrue— в единицу.
Преобразования указателей
- Постоянная нулевого указателя может быть преобразована в любой тип указателя, и результатом является значение нулевого указателя этого типа. Такое преобразование (известное как преобразование нулевого указателя) разрешено для преобразования в тип с cv-квалификаторами как единое преобразование, то есть оно не считается комбинацией числовых и квалифицирующих преобразований.
- Указатель на любой (возможно с cv-квалификаторами) тип объекта
Tможет быть преобразован в указатель на void (с теми же cv-квалификаторами). Результирующий указатель представляет ту же область памяти, что и исходное значение указателя. - Если исходный указатель — нулевое значение, результатом является нулевое значение указателя типа назначения.
- Указатель на производный полный класс может быть преобразован в указатель на его базовый класс. Результатом преобразования является указатель на подобъект базового класса внутри объекта, на который указывает указатель.
- Если базовый класс недоступен или неоднозначен, программа некорректна.
- Нулевое значение указателя преобразуется в нулевое значение указателя типа назначения.
Преобразования указателей на члены
- Постоянная нулевого указателя может быть преобразована в любой тип указателя на член, и результатом является нулевое значение указателя на член этого типа. Такое преобразование (известное как преобразование нулевого указателя на член) разрешено для преобразования в тип с cv-квалификаторами как единое преобразование, то есть оно не считается комбинацией числовых и квалифицирующих преобразований.
- Указатель на член некоторого типа
Tв базовом классеBможет быть преобразован в указатель на член того же типаTв его производном полном классеD. Результирующий указатель может быть использован с объектомD, и он будет обращаться к члену внутри подобъекте базового классаBэтого объектаD. - Если
Bнедоступен, неоднозначен или является виртуальным базовым классомDили является базовым для некоторого промежуточного виртуального базового классаD, программа некорректна. - Если
Dне содержит исходного члена и не является базовым классом класса, содержащего исходный член, поведение не определено. - Нулевое значение указателя преобразуется в нулевое значение указателя типа назначения.
Преобразования в булевый тип
Значение типа «prvalue» целочисленного, с плавающей точкой, нескопируемого перечисления, указателя и указателя на член может быть преобразовано в значение типа «bool».
Значение ноль (для целочисленных, с плавающей точкой и нескопируемых перечислений), нулевой указатель и нулевой указатель на член становятся false. Все остальные значения становятся true.
| В контексте прямой инициализации объект типа bool может быть инициализирован значением типа «prvalue» типа | (с C++11) |
Преобразования квалификаций
В общем случае:
- Значение типа «prvalue» указателя на тип с квалификаторами cv
Tможет быть преобразовано в значение типа «prvalue» указателя на тип с более сильными квалификаторами cv того же типаT(иными словами, можно добавить const и volatile). - Значение типа «prvalue» указателя на член типа с квалификаторами cv
Tв классеXможет быть преобразовано в значение типа «prvalue» указателя на член типа с более сильными квалификаторами cvTв классеX.
Формальное определение «преобразования квалификации» приведено ниже.
Типы-аналоги
Неформально, два типа считаются аналогичными, если, игнорируя квалификаторы cv верхнего уровня:
- они являются одним и тем же типом; или
- оба являются указателями, а указанные типы аналогичны; или
- оба являются указателями на члены одного и того же класса, а типы указанных членов аналогичны; или
- оба являются массивами, а типы элементов массива аналогичны.
Например:
- const int* const * и int** аналогичны;
- int (*)(int*) и int (*)(const int*) не аналогичны;
- const int (*)(int*) и int (*)(int*) не аналогичны;
- int (*)(int* const) и int (*)(int*) аналогичны (они являются одним типом);
- std::pair<int, int> и std::pair<const int, int> не аналогичны.
Формально, сходство типов определяется с помощью разложения по квалификаторам.
Разложение по квалификаторам типа T — это последовательность компонентов cv_i и P_i такая, что T является «cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U» для неотрицательного n, где
- каждый
cv_i— это набор квалификаторов const и volatile, и - каждый
P_i— это - «указатель на»,
- «указатель на член класса
C_iтипа», - «массив из
N_i», или - «массив неизвестной длины».
Если P_i обозначает массив, квалификаторы cv cv_i+1 типа элемента также считаются квалификаторами cv cv_i массива.
// T is “pointer to pointer to const int”, it has 3 qualification-decompositions: // n = 0 -> cv_0 is empty, U is “pointer to pointer to const int” // n = 1 -> cv_0 is empty, P_0 is “pointer to”, // cv_1 is empty, U is “pointer to const int” // n = 2 -> cv_0 is empty, P_0 is “pointer to”, // cv_1 is empty, P_1 is “pointer to”, // cv_2 is “const", U is “int” using T = const int**; // substitute any of the following type to U gives one of the decompositions: // U = U0 -> the decomposition with n = 0: U0 // U = U1 -> the decomposition with n = 1: pointer to [U1] // U = U2 -> the decomposition with n = 2: pointer to [pointer to [const U2]] using U2 = int; using U1 = const U2*; using U0 = U1*;
Два типа T1 и T2 являются аналогичными, если для каждого из них существует разложение по квалификаторам, где выполняются все следующие условия для двух разложений:
- Они имеют одинаковые
n. - Типы, обозначаемые
U, одинаковы. - Соответствующие компоненты
P_iсовпадают или один из них «массив изN_i», а другой — «массив неизвестной длины»(с C++20) для всехi.
// the qualification-decomposition with n = 2: // pointer to [volatile pointer to [const int]] using T1 = const int* volatile *; // the qualification-decomposition with n = 2: // const pointer to [pointer to [int]] using T2 = int** const; // For the two qualification-decompositions above // although cv_0, cv_1 and cv_2 are all different, // they have the same n, U, P_0 and P_1, // therefore types T1 and T2 are similar.
Объединение квалификаторов cv
В описании ниже, самое длинное разложение по квалификаторам типа Tn обозначается как Dn, а его компоненты обозначаются как cvn_i и Pn_i.
| Значение выражения типа «prvalue»
Тип, полученный объединением квалификаторов двух типов
| (до C++20) |
| Тип, полученный объединением квалификаторов двух типов
Значение типа «prvalue» | (с C++20) |
// longest qualification-decomposition of T1 (n = 2):
// pointer to [pointer to [char]]
using T1 = char**;
// longest qualification-decomposition of T2 (n = 2):
// pointer to [pointer to [const char]]
using T2 = const char**;
// Determining the cv3_i and T_i components of D3 (n = 2):
// cv3_1 = empty (union of empty cv1_1 and empty cv2_1)
// cv3_2 = “const” (union of empty cv1_2 and “const” cv2_2)
// P3_0 = “pointer to” (no array of unknown bound, use P1_0)
// P3_1 = “pointer to” (no array of unknown bound, use P1_1)
// All components except cv_2 are the same, cv3_2 is different from cv1_2,
// therefore add “const” to cv3_k for each k in [1, 2): cv3_1 becomes “const”.
// T3 is “pointer to const pointer to const char”, i.e., const char* const *.
using T3 = /* the qualification-combined type of T1 and T2 */;
int main()
{
const char c = 'c';
char* pc;
T1 ppc = &pc;
T2 pcc = ppc; // Error: T3 is not the same as cv-unqualified T2,
// no implicit conversion.
*pcc = &c;
*pc = 'C'; // If the erroneous assignment above is allowed,
// the const object “c” may be modified.
}Обратите внимание, что в языке C квалификаторы const/volatile можно добавить только к первому уровню:
char** p = 0; char * const* p1 = p; // OK in C and C++ const char* const * p2 = p; // error in C, OK in C++
Преобразования указателей на функции
void (*p)();
void (**pp)() noexcept = &p; // error: cannot convert to pointer to noexcept function
struct S
{
typedef void (*p)();
operator p();
};
void (*q)() noexcept = S(); // error: cannot convert to pointer to noexcept function | (с C++17) |
Проблема safe bool
До C++11, создание класса, пригодного для использования в булевых контекстах (например, if (obj) { ... }) представляло проблему: учитывая пользовательскую функцию преобразования, такую как T::operator bool() const;, последовательность неявных преобразований допускала одну дополнительную стандартную последовательность преобразований после вызова этой функции, что означает, что результирующее значение bool могло быть преобразовано в int, позволяя такой код, как obj << 1; или int i = obj;.
Одним из ранних решений для этого является std::basic_ios, которое изначально определяет operator void*, так что код, такой как if (std::cin) {...}, компилируется, потому что void* преобразуется в bool, но int n = std::cout; не компилируется, потому что void* не преобразуется в int. Это всё ещё позволяет компилировать бессмысленный код, такой как delete std::cout;.
Многие библиотеки сторонних разработчиков до C++11 были разработаны с более сложным решением, известным как идиома Safe Bool. std::basic_ios также позволяло использовать эту идиому через LWG issue 468, а operator void* была заменена (см. примечания).
С момента C++11, явное преобразование в bool также можно использовать для решения проблемы безопасного bool.
Отчёты о дефектах
Следующие отчёты о дефектах, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применён к | Поведение, опубликованное | Правильное поведение |
|---|---|---|---|
| CWG 170 | C++98 | поведение преобразований указателя на член было неясным если производный класс не имеет исходного члена | уточнено |
| CWG 172 | C++98 | тип перечисления повышался на основе его базового типа | на основе диапазона его значений |
|
CWG 330 (N4261) | 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 1773 | C++11 | выражение имени, которое появляется в потенциально вычисляемом выражении, таком что объект, именованный, не используется в odr, могло быть вычислено во время преобразования lvalue в rvalue | не вычисляется |
| CWG 1781 | C++11 |
std::nullptr_t в bool рассматривалось как неявноепреобразование, хотя оно допустимо только для прямой инициализации | больше не рассматривается как неявное преобразование |
| CWG 1787 | C++98 | поведение чтения из неопределённого unsigned char, кэшированного в регистре, было неопределённым | сделано определённым |
| CWG 1981 | C++11 | контекстные преобразования рассматривались как явные функции преобразования | не рассматриваются |
| CWG 2140 | C++11 | было неясно, извлекают ли преобразования lvalue в rvalue из памяти эти lvalue | не извлекаются |
| CWG 2310 | C++98 | для преобразований указателя из производного в базовый и преобразований указателя на член из базового в производный, тип производного класса может быть неполным | должен быть полным |
| CWG 2484 | C++20 | char8_t и char16_t имели разные стратегии целочисленного продвижения, но они могли бы подойти обоим | char8_t должен быть продвинут так же, как и char16_t |
| CWG 2485 | C++98 | целочисленные продвижения, включающие битовые поля, не были хорошо специфицированы | улучшена спецификация |
См. также
-
const_cast -
static_cast -
dynamic_cast -
reinterpret_cast - явное преобразование
- пользовательское преобразование
| Документация C по Неявным преобразованиям |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/implicit_conversion