Namespaces
Variants

Implicit conversions

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Implicit conversions
static_cast
const_cast
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Неявные преобразования выполняются всякий раз, когда выражение некоторого типа T1 используется в контексте, который не принимает этот тип, но принимает некоторый другой тип T2 ; в частности:

  • когда выражение используется в качестве аргумента при вызове функции, объявленной с параметром типа T2 ;
  • когда выражение используется в качестве операнда с оператором, который ожидает тип T2 ;
  • при инициализации нового объекта типа T2 , включая оператор return в функции, возвращающей тип T2 ;
  • когда выражение используется в операторе switch ( T2 является целочисленным типом);
  • когда выражение используется в операторе if или цикле ( T2 является типом bool ).

Программа является корректной (компилируется) только в том случае, если существует однозначная неявная последовательность преобразования из T1 в T2 .

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

Примечание: в арифметических выражениях целевой тип для неявных преобразований операндов бинарных операторов определяется отдельным набором правил: usual arithmetic conversions .

Содержание

Порядок преобразований

Неявная последовательность преобразования состоит из следующего, в таком порядке:

1) ноль или одна стандартная последовательность преобразования ;
2) ноль или одно пользовательское преобразование ;
3) ноль или одна standard conversion sequence (только если используется пользовательское преобразование).

При рассмотрении аргумента конструктора или пользовательской функции преобразования допускается только одна стандартная последовательность преобразований (в противном случае пользовательские преобразования могли бы эффективно объединяться в цепочку). При преобразовании из одного неклассового типа в другой неклассовый тип допускается только стандартная последовательность преобразований.

Стандартная последовательность преобразований состоит из следующего, в таком порядке:

1) ноль или одно преобразование из следующего набора:
  • lvalue-to-rvalue conversion ,
  • array-to-pointer conversion , и
  • function-to-pointer conversion ;
2) ноль или одно numeric promotion или numeric conversion ;
3) ноль или одно function pointer conversion ;
(since C++17)
4) ноль или одно qualification conversion .

Пользовательское преобразование состоит из нуля или одного неявного одноаргументного converting constructor или неявного conversion function вызова.

Выражение e называется неявно преобразуемым в T2 тогда и только тогда, когда T2 может быть копически инициализирован из e , то есть объявление T2 t = e ; является корректным (может быть скомпилировано) для некоторой введённой временной переменной t . Заметьте, что это отличается от прямой инициализации ( T2 t ( e ) ), где дополнительно рассматриваются явные конструкторы и функции преобразования.

Контекстные преобразования

В следующих контекстах ожидается тип bool и выполняется неявное преобразование, если объявление bool t ( e ) ; является корректным (то есть, явная функция преобразования, такая как explicit T :: operator bool ( ) const ; рассматривается). Такое выражение e называется контекстно преобразованным в bool .

  • управляющее выражение if , while , for ;
  • операнды встроенных логических операторов ! , && и || ;
  • первый операнд условного оператора ?: ;
  • предикат в объявлении static_assert ;
  • выражение в спецификаторе noexcept ;
  • выражение в спецификаторе explicit ;
(since C++20)
(since C++11)

В следующих контекстах ожидается контекстно-зависимый тип T , и выражение e типа класса E разрешено только если

(до C++14)
  • существует ровно один тип T среди допустимых типов, для которого E имеет неявные функции преобразования, возвращающие типы (возможно cv-квалифицированный) T или ссылку на (возможно cv-квалифицированный) T , и
  • e неявно преобразуем в T .
(начиная с C++14)

Такое выражение e называется контекстно неявно преобразуемым в указанный тип T . Заметьте, что явные функции преобразования не рассматриваются, даже если они учитываются при контекстных преобразованиях к bool . (начиная с C++11)

  • аргумент delete-expression ( T — любой тип указателя на объект);
  • integral constant expression , где используется литеральный класс ( 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) {}     // ошибка до C++14 (более одной функции преобразования)
                      // OK с C++14 (обе функции преобразуют к одному типу int)
    switch (i + 0) {} // всегда работает (неявное преобразование)
}

Преобразования значений

Трансформации значений — это преобразования, которые изменяют категорию значения выражения. Они происходят всякий раз, когда выражение появляется в качестве операнда оператора, который ожидает выражение другой категории значения:

  • Всякий раз, когда glvalue появляется в качестве операнда оператора, требующего prvalue для этого операнда, применяются стандартные преобразования lvalue-to-rvalue , array-to-pointer или function-to-pointer для преобразования выражения в prvalue.
  • Если не указано иное, всякий раз, когда prvalue появляется в качестве операнда оператора, который ожидает glvalue для этого операнда, применяется преобразование материализации временного объекта для преобразования выражения в xvalue.
(начиная с C++17)

Преобразование lvalue в rvalue

Lvalue lvalue (до C++11) Glvalue glvalue (начиная с C++11) любого не-функционального, не-массивного типа T может быть неявно преобразовано в rvalue rvalue (до C++11) prvalue prvalue (начиная с C++11) :

  • Если T не является типом класса, тип rvalue (до C++11) prvalue (начиная с C++11) является версией T без cv-квалификаторов.
  • В противном случае тип rvalue (до C++11) prvalue (начиная с C++11) равен T .

Если в программе требуется преобразование lvalue в rvalue для неполного типа , такая программа является некорректной.

Учитывая объект, на который ссылается lvalue (until C++11) glvalue (since C++11) , как obj :

  • Когда преобразование lvalue-to-rvalue происходит внутри операнда sizeof , значение, содержащееся в obj , не доступно, поскольку этот оператор не вычисляет свой операнд.
  • Результатом преобразования является значение, содержащееся в obj . Если один из типов T и тип obj является знаковым целочисленным типом, а другой - соответствующим беззнаковым целочисленным типом, результат представляет собой значение типа T с тем же представлением значения, что и у obj .
(до C++11)
  • Когда преобразование lvalue-to-rvalue применяется к выражению E , значение, содержащееся в obj , не доступно, если:
  • Результат преобразования определяется следующим образом:
  • Если T является (возможно, cv-квалифицированным) std::nullptr_t , результат представляет собой нулевой указатель-константу . obj не доступен преобразованием, поэтому нет побочных эффектов, даже если T квалифицирован как volatile, и glvalue может ссылаться на неактивный член union.
  • В противном случае, если T является классовым типом:
(до C++17)
(начиная с C++17)
  • В противном случае, если obj содержит недопустимое значение указателя, поведение определяется реализацией.
  • В противном случае, если биты в представлении значения объекта obj не являются допустимыми для типа obj , поведение не определено.
  • В противном случае, obj читается, и (начиная с C++20) результат представляет собой значение, содержащееся в obj . Если один из типов T и тип obj является знаковым целочисленным типом, а другой - соответствующим беззнаковым целочисленным типом, результат представляет собой значение типа T с тем же представлением значения, что и у obj .
(начиная с C++11)

Это преобразование моделирует операцию чтения значения из ячейки памяти в регистр процессора.

Преобразование массива в указатель

lvalue или rvalue типа "массив из N T " или "массив неизвестной границы из T " может быть неявно преобразован в prvalue типа "указатель на T ". Если массив является prvalue, происходит материализация временного объекта . (начиная с C++17) Результирующий указатель ссылается на первый элемент массива (см. Array-to-pointer decay для подробностей).

Преобразование функции в указатель

Выражение lvalue функционального типа может быть неявно преобразовано в prvalue указатель на эту функцию . Это не применимо к нестатическим функциям-членам, поскольку lvalue, ссылающиеся на нестатические функции-члены, не существуют.

Материализация временных объектов

Любой prvalue полного типа T может быть преобразован в xvalue того же типа T . Это преобразование инициализирует временный объект типа T из prvalue путём вычисления prvalue с временным объектом в качестве результирующего объекта и создаёт xvalue, обозначающий временный объект.

Если T является классом или массивом типа класса, он должен иметь доступный и неудалённый деструктор.

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)

Целочисленные повышения

prvalues малых целочисленных типов (таких как char ) и типов неограниченных перечислений могут быть преобразованы в prvalues более крупных целочисленных типов (таких как int ). В частности, арифметические операторы не принимают типы меньше int в качестве аргументов, и целочисленные повышения автоматически применяются после преобразования lvalue-to-rvalue, если применимо. Это преобразование всегда сохраняет значение.

Следующие неявные преобразования в этом разделе классифицируются как целочисленные повышения .

Обратите внимание, что для заданного исходного типа тип назначения целочисленного повышения уникален, и все остальные преобразования не являются повышениями. Например, разрешение перегрузки выбирает char -> int (повышение) вместо char -> short (преобразование).

Продвижение из целочисленных типов

Значение prvalue типа bool может быть преобразовано в значение prvalue типа int , при этом false становится 0 , а true становится 1 .

Для prvalue val целочисленного типа T за исключением bool :

1) Если val является результатом преобразования lvalue-в-rvalue, применённого к битовому полю ,
  • val может быть преобразовано в prvalue типа int если int может представить все значения битового поля;
  • иначе, val может быть преобразовано в unsigned int если unsigned int может представить все значения битового поля;
  • иначе, val может быть преобразовано согласно правилам, указанным в пункте (3).
2) В противном случае ( val не преобразуется из битового поля),
  • если T является char8_t , (начиная с C++20) char16_t , char32_t или (начиная с C++11) wchar_t , val может быть преобразован согласно правилам, указанным в пункте (3);
  • в противном случае, если ранг целочисленного преобразования для T ниже ранга int :
  • val может быть преобразован в prvalue типа int , если int может представить все значения типа T ;
  • иначе, val может быть преобразован в prvalue типа unsigned int .
3) В случаях, указанных в пункте (1) (преобразованное битовое поле не помещается в unsigned int ) или пункте (2) ( T является одним из указанных символьных типов), val может быть преобразовано в prvalue первого из следующих типов, который может представить все значения его базового типа:
  • int
  • unsigned int
  • long
  • unsigned long
  • long long
  • unsigned long long
  • базовый тип T
(начиная с C++11)

Продвижение из типов перечисления

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

  • int
  • unsigned int
  • long
  • unsigned long
  • long long
  • unsigned long long
  • расширенный целочисленный тип, такой что
  • его ранг целочисленного преобразования больше ранга long long ,
  • его ранг целочисленного преобразования является наименьшим среди всех расширенных целочисленных типов, и
  • он является знаковым, если существует два типа с наименьшим рангом целочисленного преобразования среди всех расширенных целочисленных типов.
(начиная с C++11)


Правостое значение неограниченного перечисления с фиксированным базовым типом может быть преобразовано в его базовый тип. Более того, если базовый тип также подвергается целочисленному повышению, то в повышенный базовый тип. Для целей разрешения перегрузки преобразование в неповышенный базовый тип является предпочтительным.

(since C++11)

Продвижение чисел с плавающей точкой

Значение категории prvalue типа float может быть преобразовано в значение категории prvalue типа double . Значение при этом не изменяется.

Это преобразование называется floating-point promotion .

Числовые преобразования

В отличие от повышений, числовые преобразования могут изменять значения с потенциальной потерей точности.

Целочисленные преобразования

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

  • Если тип назначения беззнаковый, результирующее значение является наименьшим беззнаковым значением, равным исходному значению по модулю 2 n
    где n — количество битов, используемых для представления типа назначения.
  • То есть, в зависимости от того, является ли целевой тип шире или уже, знаковые целые числа знаково расширяются [1] или усекаются, а беззнаковые целые числа соответственно либо дополняются нулями, либо усекаются.
  • Если целевой тип является знаковым, значение не изменяется, если исходное целое число может быть представлено в целевом типе. В противном случае результат определяется реализацией (до C++20) уникальным значением целевого типа, равным исходному значению по модулю 2 n
    где n — количество битов, используемых для представления целевого типа
    (начиная с C++20)
    (обратите внимание, что это отличается от переполнения знаковой целочисленной арифметики , которое является неопределённым поведением).
  • Если исходный тип — bool , значение false преобразуется в ноль, а значение true преобразуется в единицу целевого типа (обратите внимание, что если целевой тип — int , это является целочисленным продвижением, а не целочисленным преобразованием).
  • Если целевой тип — bool , это является булевым преобразованием (см. ниже).
  1. Это применимо только в случае, если арифметика является дополнительным кодом, что требуется только для точных целочисленных типов . Однако обратите внимание, что в настоящее время все платформы с компилятором C++ используют арифметику дополнительного кода.

Преобразования чисел с плавающей запятой

Значение категории prvalue типа с плавающей запятой может быть преобразовано в значение категории prvalue любого другого типа с плавающей запятой.

(до C++23)

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

Значение категории prvalue стандартного типа с плавающей запятой может быть преобразовано в значение категории prvalue любого другого стандартного типа с плавающей запятой.

static_cast может быть использован для явного преобразования значения категории prvalue типа с плавающей запятой в любой другой тип с плавающей запятой.

(начиная с C++23)

Если преобразование указано в разделе плавающих промоушенов, это является промоушеном, а не преобразованием.

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

Преобразования плавающих–целочисленные

Значение категории prvalue с плавающей точкой может быть преобразовано в значение категории prvalue любого целочисленного типа. Дробная часть усекается, то есть отбрасывается.

  • Если усечённое значение не может быть помещено в целевой тип, поведение не определено (даже если целевой тип беззнаковый, модульная арифметика не применяется).
  • Если целевой тип bool , это булево преобразование (см. ниже ).

Правэлью целочисленного типа или типа неограниченного перечисления может быть преобразовано в правэлью любого типа с плавающей точкой. Результат является точным, если это возможно.

  • Если значение может поместиться в целевой тип, но не может быть представлено точно, определяется реализацией, будет выбрано ближайшее большее или ближайшее меньшее представимое значение, хотя при поддержке арифметики IEEE округление по умолчанию к ближайшему .
  • Если значение не может поместиться в целевой тип, поведение не определено.
  • Если исходный тип bool , значение false преобразуется в ноль, а значение true преобразуется в единицу.

Преобразования указателей

Нулевой указатель-константа null pointer constant может быть преобразован в любой тип указателя, и результатом будет нулевое значение указателя этого типа. Такое преобразование (известное как null pointer conversion ) допускается для преобразования в cv-квалифицированный тип как единое преобразование, то есть оно не считается комбинацией числового и квалифицирующего преобразований.

Указатель-правостороннее значение на любой (опционально cv-квалифицированный) тип объекта T может быть преобразован в указатель-правостороннее значение на (идентично cv-квалифицированный) void . Результирующий указатель представляет ту же позицию в памяти, что и исходное значение указателя.

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

Правое значение ptr типа «указатель на (возможно, cv-квалифицированный) Derived » может быть преобразовано в правое значение типа «указатель на (возможно, cv-квалифицированный) Base », где Base является базовым классом для Derived , а Derived представляет собой полный тип класса. Если Base недоступен или неоднозначен, программа является некорректной.

  • Если ptr является нулевым указателем, результат также будет нулевым указателем.
  • В противном случае, если Base является виртуальным базовым классом для Derived и ptr не указывает на объект, тип которого подобен Derived и который находится в пределах своего времени жизни или в периоде своего конструирования или разрушения, поведение не определено.
  • В противном случае результат представляет собой указатель на подобъект базового класса объекта производного класса.

Преобразования указателей на члены

Нулевой указатель-константа null pointer constant может быть преобразован в любой тип указателя-на-член, и результатом будет нулевое значение указателя-на-член этого типа. Такое преобразование (известное как null member pointer conversion ) допускает преобразование в cv-квалифицированный тип как единое преобразование, то есть не считается комбинацией числового и квалифицирующего преобразований.

Значение категории prvalue типа «указатель на член класса Base типа (возможно cv-квалифицированный) T » может быть преобразовано в значение категории prvalue типа «указатель на член класса Derived типа (идентично cv-квалифицированный) T », где Base является базовым классом для Derived , и Derived является полным типом класса. Если Base является недоступным, неоднозначным или виртуальным базовым классом для Derived или является базовым классом некоторого промежуточного виртуального базового класса для Derived , программа является некорректной.

  • Если Derived не содержит исходный член и не является базовым классом класса, содержащего исходный член, поведение не определено.
  • В противном случае, полученный указатель может быть разыменован с объектом Derived , и он будет обращаться к члену внутри базового подобъекта Base этого объекта Derived .

Логические преобразования

Значение категории prvalue целочисленного, вещественного, неперечислимого типа, а также указателя и указателя на член класса может быть преобразовано в значение категории prvalue типа bool .

Значение ноль (для целочисленных, плавающих типов и неограниченных перечислений), нулевой указатель и нулевой указатель на члены становятся false . Все остальные значения становятся true .

В контексте прямой инициализации , объект типа bool может быть инициализирован из prvalue типа std::nullptr_t , включая nullptr . Результирующее значение равно false . Однако это не считается неявным преобразованием.

(начиная с C++11)

Преобразования квалификаторов

Вообще говоря:

  • Значение категории prvalue типа указатель на prvalue cv-квалифицированный тип T может быть преобразовано в prvalue указатель на более cv-квалифицированный тот же тип T (другими словами, константность и волатильность могут быть добавлены).
  • Значение категории prvalue типа указатель на член cv-квалифицированного типа T в классе X может быть преобразовано в prvalue указатель на член более cv-квалифицированного типа T в классе 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 - это "указатель на указатель на const int", имеет 3 квалификационных декомпозиции:
// n = 0 -> cv_0 пусто, U - "указатель на указатель на const int"
// n = 1 -> cv_0 пусто, P_0 - "указатель на",
//          cv_1 пусто, U - "указатель на const int"
// n = 2 -> cv_0 пусто, P_0 - "указатель на",
//          cv_1 пусто, P_1 - "указатель на",
//          cv_2 - "const", U - "int"
using T = const int**;
// подстановка любого из следующих типов в U дает одну из декомпозиций:
// U = U0 -> декомпозиция с n = 0: U0
// U = U1 -> декомпозиция с n = 1: указатель на [U1]
// U = U2 -> декомпозиция с n = 2: указатель на [указатель на [const U2]]
using U2 = int;
using U1 = const U2*;
using U0 = U1*;

Два типа T1 и T2 называются подобными , если существует квалификационная декомпозиция для каждого из них, при которой для двух квалификационных декомпозиций выполняются все следующие условия:

  • Они имеют одинаковое n .
  • Типы, обозначаемые U , одинаковы.
  • Соответствующие компоненты P_i одинаковы или один является «массивом N_i », а другой — «массивом неизвестной границы» (начиная с C++20) для всех i .
// квалификационная декомпозиция с n = 2:
// указатель на [volatile указатель на [const int]]
using T1 = const int* volatile *;
// квалификационная декомпозиция с n = 2:
// const указатель на [указатель на [int]]
using T2 = int** const;
// Для двух приведенных выше квалификационных декомпозиций
// хотя cv_0, cv_1 и cv_2 все различны,
// они имеют одинаковые n, U, P_0 и P_1,
// следовательно, типы T1 и T2 являются подобными.

Комбинирование cv-квалификаторов

В приведенном ниже описании самая длинная квалификационная декомпозиция типа Tn обозначается как Dn , а её компоненты обозначаются как cvn_i и Pn_i .

Выражение-правостоечное значение типа T1 может быть преобразовано в тип T2 при выполнении всех следующих условий:

  • T1 и T2 являются подобными.
  • Для каждого ненулевого i , если const присутствует в cv1_i , то const также присутствует в cv2_i , и аналогично для volatile .
  • Для каждого ненулевого i , если cv1_i и cv2_i различаются, то const добавляется в cv2_k для каждого k в [ 1 , i ) .

Тип с объединёнными квалификаторами двух типов T1 и T2 — это тип T3 , подобный T1 , такой что

  • cv3_0 пуст,
  • для каждого ненулевого i , cv3_i является объединением cv1_i и cv2_i , и
  • если cv3_i отличается от cv1_i или c2_i , то const добавляется в cv3_k для каждого k в [ 1 , i ) .
(до C++20)

Тип с объединёнными квалификаторами двух типов T1 и T2 — это тип T3 , подобный T1 , где D3 удовлетворяет всем следующим условиям:

  • cv3_0 пуст.
  • Для каждого ненулевого i , cv3_i является объединением cv1_i и cv2_i .
  • Если P1_i или P2_i является "массивом с неизвестной границей", P3_i является "массивом с неизвестной границей", иначе это P1_i .
  • Если cv3_i отличается от cv1_i или cv2_i , или P3_i отличается от P1_i или P2_i , то const добавляется в cv3_k для каждого k в [ 1 , i ) .

Правостоечное значение типа T1 может быть преобразовано в тип T2 , если тип с объединёнными квалификаторами T1 и T2 является неквалифицированным T2 .

(начиная с C++20)
// самая длинная квалификационная декомпозиция T1 (n = 2):
// указатель на [указатель на [char]]
using T1 = char**;
// самая длинная квалификационная декомпозиция T2 (n = 2):
// указатель на [указатель на [const char]]
using T2 = const char**;
// Определение компонентов cv3_i и T_i для D3 (n = 2):
// cv3_1 = пусто (объединение пустого cv1_1 и пустого cv2_1)
// cv3_2 = "const" (объединение пустого cv1_2 и "const" cv2_2)
// P3_0 = "указатель на" (нет массива неизвестной границы, используем P1_0)
// P3_1 = "указатель на" (нет массива неизвестной границы, используем P1_1)
// Все компоненты кроме cv_2 одинаковы, cv3_2 отличается от cv1_2,
// поэтому добавляем "const" к cv3_k для каждого k в [1, 2): cv3_1 становится "const".
// T3 - это "указатель на const указатель на const char", т.е. const char* const *.
using T3 = /* тип с объединенными квалификациями T1 и T2 */;
int main()
{
    const char c = 'c';
    char* pc;
    T1 ppc = &pc;
    T2 pcc = ppc; // Ошибка: T3 не совпадает с T2 без cv-квалификаторов,
                  //        неявное преобразование невозможно.
    *pcc = &c;
    *pc = 'C';    // Если ошибочное присваивание выше разрешено,
                  // const-объект "c" может быть изменен.
}

Обратите внимание, что в языке программирования C const / volatile могут быть добавлены только на первом уровне:

char** p = 0;
char * const* p1 = p;       // OK в C и C++
const char* const * p2 = p; // ошибка в C, OK в C++

Преобразования указателей на функции

  • prvalue типа указатель на не-бросающую функцию может быть преобразован в prvalue указатель на потенциально-бросающую функцию.
  • prvalue типа указатель на не-бросающую функцию-член может быть преобразован в prvalue указатель на потенциально-бросающую функцию-член.
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)

Проблема безопасного 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 idiom . 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-to-rvalue
не вычисляется
CWG 1781 C++11 std::nullptr_t в bool считалось неявным
преобразованием, хотя оно допустимо только для прямой инициализации
больше не считается
неявным преобразованием
CWG 1787 C++98 поведение чтения из неопределенного
unsigned char кэшированного в регистре было неопределенным
сделано определенным
CWG 1981 C++11 контекстуальные преобразования рассматривали явные функции преобразования не рассматривалось
CWG 2140 C++11 было неясно, выполняют ли преобразования lvalue-to-rvalue из
std::nullptr_t lvalues загрузку этих lvalues из памяти
не загружаются
CWG 2310 C++98 для преобразований указателей из производного в базовый класс и
преобразований указателей на члены из базового в производный класс,
тип производного класса мог быть неполным
должен быть полным
CWG 2484 C++20 char8_t и char16_t имели разные стратегии
целочисленного продвижения, но они могут подходить для обоих
char8_t должен продвигаться
таким же образом, как char16_t
CWG 2485 C++98 целочисленные повышения, связанные с битовыми полями, были плохо специфицированы улучшена спецификация
CWG 2813 C++23 материализация временного объекта происходила бы при вызове
явной объектной функции-члена prvalue класса
не будет происходить
в этом случае
CWG 2861 C++98 указатель на объект с недоступным типом может быть
преобразован в указатель на подобъект базового класса
поведение в этом случае
не определено
CWG 2879 C++17 временная материализационная конверсия применялась к prvalue
как операнду оператора, который ожидает glvalue
не применялась в некоторых случаях
CWG 2899 C++98 Преобразования lvalue-to-rvalue могли применяться к lvalues,
обозначающим объекты с недопустимыми представлениями значений
Поведение в этом случае
не определено
CWG 2901 C++98 результат преобразования lvalue-to-rvalue для unsigned int
lvalue, ссылающегося на объект int со значением - 1 был неясен
прояснено

Смотрите также

Документация C для Неявные преобразования