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) ноль или одно преобразование квалификации.

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

Выражение 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

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 в операнде оператора sizeof, значение, содержащееся в объекте, на который ссылается lvalue, не используется, так как этот оператор не оценивает свой операнд.

(до C++11)

Когда преобразование lvalue в rvalue применяется к выражению E, значение, содержащееся в ссылаемом объекте, не используется, если:

  • E не подлежит оценке, или
  • оценка E приводит к оценке члена Ex из множества возможных результатов E, и Ex обозначает переменную x, которая не используется Ex.
(с C++11)

Результатом преобразования является значение, содержащееся в объекте, указанном lvalue.

(до C++11)

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

  • Если T (возможно, с квалификаторами cv) std::nullptr_t, результат — константа нулевого указателя. Объект, на который ссылается glvalue, не обращается при преобразовании, поэтому побочных эффектов нет, даже если T имеет квалификатор volatile, и glvalue может ссылаться на неактивный член объединения.
  • В противном случае, если T имеет тип класса,
  • преобразование копирует временный объект типа T из glvalue, и результат преобразования — prvalue временного объекта.
(до C++17)
  • преобразование копирует объект-результат из glvalue.
(с C++17)
  • В противном случае, если объект, на который ссылается glvalue, содержит некорректное значение указателя, поведение определяется реализацией.
  • В противном случае, объект, указанный glvalue, читается,(с C++20) и результат — значение, содержащееся в объекте.
(с 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 массива;
  • при инициализации объекта типа std::initializer_list<T> из списка инициализации;
  • при применении typeid к prvalue (это часть неоцениваемого выражения);
  • при применении sizeof к prvalue (это часть неоцениваемого выражения);
  • когда prvalue используется как выражение, результат которого отбрасывается.

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

(с C++17)

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

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

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

Целочисленные повышения из целочисленных типов

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 является(с 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)

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

(с C++11)

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

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

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

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

Преобразования числовых типов

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

Преобразования целых типов

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

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

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

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

(до C++23)

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

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

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

(с 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» типа std::nullptr_t, включая nullptr. Результирующее значение равно false. Однако это не считается неявным преобразованием.

(с C++11)

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

В общем случае:

  • Значение типа «prvalue» указателя на тип с квалификаторами cv T может быть преобразовано в значение типа «prvalue» указателя на тип с более сильными квалификаторами cv того же типа T (иными словами, можно добавить const и volatile).
  • Значение типа «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)

Проблема 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

Spec-Zone.ru

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