Spec-Zone.ru › C++

Неявные преобразования

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

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

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

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

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

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

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

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

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

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

1) ноль или одно преобразование из следующего набора:
  • преобразование lvalue в rvalue,
  • преобразование массива в указатель и
  • преобразование функции в указатель;
2) ноль или одно числовое продвижение или числовое преобразование;
3) ноль или одно преобразование указателя на функцию; (начиная с C++17)
4) ноль или одно преобразование квалификаторов.

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

Выражение 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;
(начиная с C++20)
(начиная с C++11)

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

  • E имеет ровно одну неявную(начиная с C++11) пользовательскую функцию преобразования в допустимый тип.
(до C++14)
  • среди допустимых типов существует ровно один тип T, такой что у E есть неявные функции преобразования с возвращаемыми типами (возможно, cv-квалифицированными) T или ссылкой на (возможно, cv-квалифицированный) T, и
  • e неявно преобразуемо в T.
(начиная с 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.
  • Если не указано иное, всякий раз, когда prvalue используется как операнд оператора, ожидающего для этого операнда glvalue, применяется преобразование с материализацией временного объекта, преобразующее выражение в xvalue.
(начиная с C++17)

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

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

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

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

Пусть obj — объект, на который ссылается lvalue(до C++11)glvalue(начиная с C++11):

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

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

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

lvalue или rvalue типа «массив N T» или «массив неизвестного размера из T» может быть неявно преобразовано в prvalue типа «указатель на T». Если массив является prvalue, происходит материализация временного объекта.(начиная с C++17) Полученный указатель указывает на первый элемент массива (подробности см. в разделе преобразование массива в указатель).

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

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;
  • при доступе к нестатическому члену данных prvalue классового типа;
  • при вызове функции-члена с неявным объектом для prvalue классового типа;
  • при выполнении преобразования массива в указатель (см. выше) или индексации prvalue-массива;
  • при инициализации объекта типа std::initializer_list<T> из списка инициализации в фигурных скобках;
  • когда prvalue является невычисляемым операндом в sizeof и typeid;
  • когда prvalue используется как выражение с отбрасываемым значением.

Обратите внимание, что материализация временного объекта не происходит при инициализации объекта из prvalue того же типа (посредством прямой инициализации или инициализации копированием): такой объект инициализируется непосредственно из инициализатора. Это обеспечивает «гарантированное исключение копирования».

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

Целочисленное продвижение

prvalue малых целочисленных типов (например, char) и типов перечисления без области видимости могут преобразовываться в prvalue целочисленных типов большего размера (например, int). В частности, арифметические операторы не принимают в качестве аргументов типы, меньшие, чем int, и после преобразования lvalue в 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 может быть преобразовано в unsignedint, если unsignedint может представить все значения битового поля;
  • в противном случае 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 типа unsignedint.
3) В случаях, указанных в пункте (1) (преобразованное битовое поле не помещается в unsignedint) или пункте (2) (T — один из указанных символьных типов), val может быть преобразовано в prvalue первого из следующих типов, способного представить все значения базового типа:
  • int
  • unsignedint
  • long
  • unsignedlong
  • longlong
  • unsignedlonglong
  • базовый тип T
(начиная с C++11)

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

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

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

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

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

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

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

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

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

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

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

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

  • Если целевой тип беззнаковый, результатом является наименьшее беззнаковое значение, равное исходному значению по модулю 2n, где n — число битов, используемых для представления целевого типа.
    • То есть в зависимости от того, шире или уже целевой тип, знаковые целые числа расширяются с сохранением знака[1] либо усекаются, а беззнаковые целые числа соответственно расширяются нулями либо усекаются.
  • Если целевой тип знаковый, значение не изменяется, когда исходное целое число может быть представлено целевым типом. В противном случае результат является определяемым реализацией(до C++20)единственным значением целевого типа, равным исходному значению по модулю 2n, где 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, это логическое преобразование (см. ниже).

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

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

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

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

Значение категории prvalue — указатель на объектный тип (возможно, cv-квалифицированный) T — может быть преобразовано в значение категории prvalue типа указателя на void (с идентичной cv-квалификацией). Полученный указатель указывает на то же место в памяти, что и исходное значение указателя.

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

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

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

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

Константа нулевого указателя может быть преобразована в любой тип указателя на член, и результатом будет значение нулевого указателя на член этого типа. Такое преобразование (известное как преобразование нулевого указателя на член) может преобразовывать тип в 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 типа указателя на 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 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 типа 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).

Значение категории prvalue типа T1 может быть преобразовано в тип T2, если типом, полученным объединением квалификаций T1 и T2, является тип без cv-квалификации T2.

(начиная с 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++

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

  • Значение категории 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, использовали более сложное решение, известное как идиома безопасного преобразования в bool. std::basic_ios также разрешал эту идиому посредством предложения LWG 468, а operator void* был заменён (см. примечания).

Начиная с C++11 для решения проблемы безопасного преобразования в bool можно также использовать явное преобразование в bool.

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

Отчёт об ошибке Применён к Поведение согласно опубликованному стандарту Правильное поведение
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 std::nullptr_t эти lvalue из памяти не извлекают
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, обозначающим объекты с недопустимыми представлениями значений,
могли применяться преобразования lvalue в rvalue
в этом случае поведение
не определено
CWG 2901 C++98 результат преобразования lvalue в rvalue из lvalue типа unsigned int,
ссылающегося на объект int со значением -1, был неясен
прояснено

См. также

  • 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/cpp/language/implicit_conversion

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API