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

Las conversiones implícitas se realizan cuando una expresión de algún tipo T1 se utiliza en un contexto que no acepta ese tipo, pero acepta algún otro tipo T2 ; en particular:

  • cuando la expresión se utiliza como argumento al llamar a una función declarada con T2 como parámetro;
  • cuando la expresión se utiliza como operando con un operador que espera T2 ;
  • cuando se inicializa un nuevo objeto de tipo T2 , incluyendo la sentencia return en una función que devuelve T2 ;
  • cuando la expresión se utiliza en una sentencia switch ( T2 es tipo integral);
  • cuando la expresión se utiliza en una sentencia if o un bucle ( T2 es bool ).

El programa está bien formado (compila) solo si existe una secuencia de conversión implícita inequívoca de T1 a T2 .

Si hay múltiples sobrecargas de la función u operador que se está llamando, después de construir la secuencia de conversión implícita desde T1 a cada T2 disponible, las reglas de resolución de sobrecarga deciden qué sobrecarga se compila.

Nota: en expresiones aritméticas, el tipo de destino para las conversiones implícitas en los operandos de operadores binarios se determina mediante un conjunto separado de reglas: usual arithmetic conversions .

Contenidos

Orden de las conversiones

La secuencia de conversión implícita consiste en lo siguiente, en este orden:

1) cero o una standard conversion sequence ;
2) cero o una user-defined conversion ;
3) cero o una standard conversion sequence (solo si se utiliza una conversión definida por el usuario).

Al considerar el argumento para un constructor o para una función de conversión definida por el usuario, solo se permite una secuencia de conversión estándar (de lo contrario, las conversiones definidas por el usuario podrían encadenarse efectivamente). Al convertir de un tipo no-clase a otro tipo no-clase, solo se permite una secuencia de conversión estándar.

Una secuencia de conversión estándar consta de lo siguiente, en este orden:

1) cero o una conversión del siguiente conjunto:
  • lvalue-to-rvalue conversion ,
  • array-to-pointer conversion , y
  • function-to-pointer conversion ;
2) cero o una numeric promotion o numeric conversion ;
3) cero o una conversión de puntero a función ;
(since C++17)
4) cero o una qualification conversion .

Una conversión definida por el usuario consiste en cero o un constructor de conversión de un solo argumento no explícito o una llamada a función de conversión no explícita conversion function .

Se dice que una expresión e es implícitamente convertible a T2 si y solo si T2 puede ser copy-initialized desde e , es decir, la declaración T2 t = e ; está bien formada (puede compilarse), para algún t temporal inventado. Nótese que esto es diferente de la direct initialization ( T2 t ( e ) ), donde se considerarían adicionalmente constructores explícitos y funciones de conversión.

Conversiones contextuales

En los siguientes contextos, se espera el tipo bool y se realiza la conversión implícita si la declaración bool t ( e ) ; es válida (es decir, una función de conversión explícita como explicit T :: operator bool ( ) const ; se considera). Dicha expresión e se dice que está contextualmente convertida a bool .

  • la expresión de control de if , while , for ;
  • los operandos de los operadores lógicos incorporados ! , && y || ;
  • el primer operando del operador condicional ?: ;
  • el predicado en una declaración static_assert ;
  • la expresión en un especificador noexcept ;
(desde C++20)
(desde C++11)

En los siguientes contextos, se espera un tipo específico del contexto T , y la expresión e de tipo clase E solo está permitida si

  • E tiene una única función de conversión definida por el usuario (desde C++11) no explícita a un tipo permitido.
(hasta C++14)
  • existe exactamente un tipo T entre los tipos permitidos tal que E tiene funciones de conversión no explícitas cuyos tipos de retorno son (posiblemente calificados cv) T o referencia a (posiblemente calificado cv) T , y
  • e es implícitamente convertible a T .
(desde C++14)

Tal expresión e se dice que está convertida implícitamente contextualmente al tipo especificado T . Nótese que las funciones de conversión explícitas no se consideran, aunque sí se consideran en conversiones contextuales a bool . (desde C++11)

  • el argumento de la delete-expression ( T es cualquier tipo de puntero a objeto);
  • expresión constante integral , donde se utiliza una clase literal ( T es cualquier tipo integral o de enumeración no acotada, la función de conversión definida por el usuario seleccionada debe ser constexpr );
  • la expresión de control de la sentencia switch ( T es cualquier tipo integral o de enumeración).
#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 hasta C++14 (más de una función de conversión)
                      // OK desde C++14 (ambas funciones convierten al mismo tipo int)
    switch (i + 0) {} // siempre correcto (conversión implícita)
}

Transformaciones de valores

Las transformaciones de valor son conversiones que cambian la categoría de valor de una expresión. Ocurren cada vez que una expresión aparece como operando de un operador que espera una expresión de una categoría de valor diferente:

  • Siempre que un glvalue aparece como operando de un operador que requiere un prvalue para ese operando, las conversiones estándar lvalue-to-rvalue , array-to-pointer , o function-to-pointer se aplican para convertir la expresión a un prvalue.
  • A menos que se especifique lo contrario, siempre que un prvalue aparece como operando de un operador que espera un glvalue para ese operando, la conversión de materialización temporal se aplica para convertir la expresión a un xvalue.
(since C++17)

Conversión de lvalue a rvalue

Un lvalue (hasta C++11) Un glvalue (desde C++11) de cualquier tipo no-función, no-array T puede convertirse implícitamente a un rvalue (hasta C++11) un prvalue (desde C++11) :

  • Si T no es un tipo clase, el tipo del rvalue (until C++11) prvalue (since C++11) es la versión sin calificadores cv de T .
  • En caso contrario, el tipo del rvalue (until C++11) prvalue (since C++11) es T .

Si una conversión de lvalue a rvalue de un tipo incompleto es requerida por un programa, ese programa está mal formado.

Dado el objeto al cual la lvalue (until C++11) glvalue (since C++11) se refiere como obj :

  • Cuando ocurre una conversión de lvalue a rvalue dentro del operando de sizeof , no se accede al valor contenido en obj , ya que ese operador no evalúa su operando.
  • El resultado de la conversión es el valor contenido en obj . Si uno de T y el tipo de obj es un tipo entero con signo, y el otro es el tipo entero sin signo correspondiente, el resultado es el valor de tipo T con la misma representación de valor de obj .
(hasta C++11)
  • Cuando se aplica una conversión de lvalue a rvalue a una expresión E , no se accede al valor contenido en obj si:
  • El resultado de la conversión se determina de la siguiente manera:
  • Si T es (posiblemente calificado con cv) std::nullptr_t , el resultado es una constante de puntero nulo . obj no es accedido por la conversión, por lo que no hay efecto secundario incluso si T está calificado como volátil, y el glvalue puede referirse a un miembro inactivo de una unión.
  • De lo contrario, si T es un tipo clase:
(hasta C++17)
(desde C++17)
  • De lo contrario, si obj contiene un valor de puntero inválido, el comportamiento está definido por la implementación.
  • De lo contrario, si los bits en la representación de valor de obj no son válidos para el tipo de obj , el comportamiento es indefinido.
  • De lo contrario, obj es leído, y (desde C++20) el resultado es el valor contenido en obj . Si uno de T y el tipo de obj es un tipo entero con signo, y el otro es el tipo entero sin signo correspondiente, el resultado es el valor de tipo T con la misma representación de valor de obj .
(desde C++11)

Esta conversión modela el acto de leer un valor desde una ubicación de memoria hacia un registro de la CPU.

Conversión de array a puntero

Un lvalue o rvalue de tipo "array de N T " o "array de límite desconocido de T " puede convertirse implícitamente a un prvalue de tipo "puntero a T ". Si el array es un prvalue, ocurre materialización temporal . (desde C++17) El puntero resultante se refiere al primer elemento del array (ver Decaimiento de array a puntero para más detalles).

Conversión de función a puntero

Un lvalue de tipo función puede convertirse implícitamente a un prvalue puntero a esa función . Esto no se aplica a funciones miembro no estáticas porque no existen lvalues que se refieran a funciones miembro no estáticas.

Materialización temporal

Un prvalue de cualquier tipo completo T puede convertirse en un xvalue del mismo tipo T . Esta conversión inicializa un objeto temporal de tipo T desde el prvalue evaluando el prvalue con el objeto temporal como su objeto resultado, y produce un xvalue que denota el objeto temporal.

Si T es una clase o un array de tipo clase, debe tener un destructor accesible y no eliminado.

struct S { int m; };
int i = S().m; // member access expects glvalue as of C++17;
               // S() prvalue is converted to xvalue

La materialización temporal ocurre en las siguientes situaciones:

Nótese que la materialización temporal no ocurre al inicializar un objeto desde un prvalue del mismo tipo (mediante inicialización directa o inicialización por copia ): dicho objeto se inicializa directamente desde el inicializador. Esto garantiza la "elisión de copia garantizada".

(desde C++17)

Promoción integral

prvalues de tipos integrales pequeños (como char ) y tipos de enumeración no delimitados pueden convertirse en prvalues de tipos integrales más grandes (como int ). En particular, los operadores aritméticos no aceptan tipos más pequeños que int como argumentos, y las promociones integrales se aplican automáticamente después de la conversión de lvalue-a-rvalue, si es aplicable. Esta conversión siempre preserva el valor.

Las siguientes conversiones implícitas en esta sección se clasifican como integral promotions .

Tenga en cuenta que para un tipo de origen dado, el tipo de destino de la promoción integral es único, y todas las demás conversiones no son promociones. Por ejemplo, la resolución de sobrecarga elige char -> int (promoción) sobre char -> short (conversión).

Promoción de tipos integrales

Un prvalue de tipo bool puede convertirse en un prvalue de tipo int , donde false se convierte en 0 y true se convierte en 1 .

Para un prvalue val de un tipo integral T excepto bool :

1) Si val es el resultado de una conversión de lvalue a rvalue aplicada a un campo de bits ,
  • val puede convertirse a un prvalue de tipo int si int puede representar todos los valores del campo de bits;
  • de lo contrario, val puede convertirse a unsigned int si unsigned int puede representar todos los valores del campo de bits;
  • de lo contrario, val puede convertirse de acuerdo con las reglas especificadas en el punto (3).
2) En caso contrario ( val no se convierte desde un campo de bits),
  • si T es char8_t , (desde C++20) char16_t , char32_t o (desde C++11) wchar_t , val puede convertirse según las reglas especificadas en el punto (3);
  • en caso contrario, si el rango de conversión entera de T es menor que el rango de int :
  • val puede convertirse a un prvalue de tipo int si int puede representar todos los valores de T ;
  • en caso contrario, val puede convertirse a un prvalue de tipo unsigned int .
3) En los casos especificados por el inciso (1) (un campo de bits convertido que no cabe en unsigned int ) o el inciso (2) ( T es uno de los tipos de carácter dados), val puede convertirse a un prvalue del primero de los siguientes tipos que pueda representar todos los valores de su tipo subyacente:
  • int
  • unsigned int
  • long
  • unsigned long
  • long long
  • unsigned long long
  • el tipo subyacente de T
(desde C++11)

Promoción desde tipos de enumeración

Un prvalue de un tipo de enumeración no delimitada enumeration cuyo tipo subyacente no está fijo puede convertirse a un prvalue del primer tipo de la siguiente lista capaz de contener su rango completo de valores:

  • int
  • unsigned int
  • long
  • unsigned long
  • su rango de conversión entera es mayor que el rango de long long ,
  • su rango de conversión entera es el más bajo entre todos los tipos enteros extendidos, y
  • es con signo si hay dos tipos con el rango de conversión entera más bajo entre todos los tipos enteros extendidos.
(desde C++11)


Un prvalue de un tipo de enumeración no acotado cuyo tipo subyacente está fijo puede convertirse a su tipo subyacente. Además, si el tipo subyacente también está sujeto a promoción integral, al tipo subyacente promocionado. La conversión al tipo subyacente no promocionado es mejor para los propósitos de resolución de sobrecarga .

(since C++11)

Promoción de punto flotante

Un prvalue de tipo float puede convertirse a un prvalue de tipo double . El valor no cambia.

Esta conversión se denomina floating-point promotion .

Conversiones numéricas

A diferencia de las promociones, las conversiones numéricas pueden cambiar los valores, con posible pérdida de precisión.

Conversiones integrales

Un prvalue de un tipo entero o de un tipo de enumeración sin ámbito puede convertirse a cualquier otro tipo entero. Si la conversión está listada bajo promociones integrales, es una promoción y no una conversión.

  • Si el tipo de destino es sin signo, el valor resultante es el valor sin signo más pequeño igual al valor de origen módulo 2 n
    donde n es el número de bits utilizados para representar el tipo de destino.
  • Es decir, dependiendo de si el tipo de destino es más amplio o más estrecho, los enteros con signo se extienden con signo [1] o se truncan y los enteros sin signo se extienden con ceros o se truncan respectivamente.
  • Si el tipo de destino es con signo, el valor no cambia si el entero fuente puede representarse en el tipo de destino. De lo contrario, el resultado es definido por la implementación (hasta C++20) el valor único del tipo de destino igual al valor fuente módulo 2 n
    donde n es el número de bits utilizados para representar el tipo de destino
    (desde C++20)
    (nótese que esto es diferente del desbordamiento aritmético de enteros con signo , que es indefinido).
  • Si el tipo fuente es bool , el valor false se convierte a cero y el valor true se convierte al valor uno del tipo de destino (nótese que si el tipo de destino es int , esto es una promoción entera, no una conversión entera).
  • Si el tipo de destino es bool , esto es una conversión booleana (ver más abajo).
  1. Esto solo aplica si la aritmética es complemento a dos, lo cual solo es requerido para los tipos de enteros de ancho exacto . Sin embargo, nótese que actualmente todas las plataformas con un compilador de C++ utilizan aritmética de complemento a dos.

Conversiones de punto flotante

Un prvalue de un tipo de punto flotante puede convertirse en un prvalue de cualquier otro tipo de punto flotante.

(until C++23)

Un prvalue de un tipo de punto flotante puede convertirse en un prvalue de cualquier otro tipo de punto flotante con un rango de conversión de punto flotante mayor o igual.

Un prvalue de un tipo estándar de punto flotante puede convertirse en un prvalue de cualquier otro tipo estándar de punto flotante.

static_cast puede utilizarse para convertir explícitamente un prvalue de tipo de punto flotante a cualquier otro tipo de punto flotante.

(since C++23)

Si la conversión se enumera bajo promociones de punto flotante, es una promoción y no una conversión.

  • Si el valor fuente puede representarse exactamente en el tipo destino, no cambia.
  • Si el valor fuente está entre dos valores representables del tipo destino, el resultado es uno de esos dos valores (está definido por la implementación cuál, aunque si se admite aritmética IEEE, el redondeo por defecto es al más cercano ).
  • En caso contrario, el comportamiento es indefinido.

Conversiones flotante-integral

Un prvalue de tipo punto flotante puede convertirse a un prvalue de cualquier tipo entero. La parte fraccionaria se trunca, es decir, la parte fraccionaria se descarta.

  • Si el valor truncado no puede caber en el tipo de destino, el comportamiento es indefinido (incluso cuando el tipo de destino es unsigned, la aritmética modular no se aplica).
  • Si el tipo de destino es bool , esta es una conversión booleana (ver abajo ).

Un prvalue de tipo entero o de enumeración sin ámbito puede convertirse en un prvalue de cualquier tipo de punto flotante. El resultado es exacto si es posible.

  • Si el valor puede caber en el tipo de destino pero no puede representarse exactamente, está definido por la implementación si se seleccionará el valor representable más cercano superior o el más cercano inferior, aunque si se admite aritmética IEEE, el redondeo por defecto es al más cercano .
  • Si el valor no puede caber en el tipo de destino, el comportamiento es indefinido.
  • Si el tipo de origen es bool , el valor false se convierte a cero, y el valor true se convierte a uno.

Conversiones de punteros

Una constante de puntero nulo puede convertirse a cualquier tipo de puntero, y el resultado es el valor de puntero nulo de ese tipo. Dicha conversión (conocida como conversión de puntero nulo ) está permitida para convertir a un tipo calificado cv como una conversión única, es decir, no se considera una combinación de conversiones numéricas y calificadoras.

Un prvalue puntero a cualquier tipo de objeto (opcionalmente calificado con cv) T puede convertirse en un prvalue puntero a void (idénticamente calificado con cv). El puntero resultante representa la misma ubicación en memoria que el valor del puntero original.

  • Si el puntero original es un valor de puntero nulo, el resultado es un valor de puntero nulo del tipo de destino.

Un prvalue ptr de tipo "puntero a (posiblemente calificado-cv) Derived " puede convertirse en un prvalue de tipo "puntero a (posiblemente calificado-cv) Base ", donde Base es una clase base de Derived , y Derived es un tipo de clase completa . Si Base es inaccesible o ambigua, el programa está mal formado.

  • Si ptr es un valor de puntero nulo, el resultado también es un valor de puntero nulo.
  • De lo contrario, si Base es una clase base virtual de Derived y ptr no apunta a un objeto cuyo tipo sea similar a Derived y que esté dentro de su tiempo de vida o dentro de su período de construcción o destrucción, el comportamiento es indefinido.
  • De lo contrario, el resultado es un puntero al subobjeto de la clase base del objeto de la clase derivada.

Conversiones de puntero a miembro

Una constante de puntero nulo puede convertirse a cualquier tipo puntero-a-miembro, y el resultado es el valor de puntero a miembro nulo de ese tipo. Dicha conversión (conocida como conversión de puntero a miembro nulo ) está permitida para convertir a un tipo calificado-cv como una conversión única, es decir, no se considera una combinación de conversiones numéricas y calificadoras.

Un prvalue de tipo "puntero a miembro de Base de tipo (posiblemente calificado-cv) T " puede convertirse en un prvalue de tipo "puntero a miembro de Derived de tipo (idénticamente calificado-cv) T ", donde Base es una clase base de Derived , y Derived es un tipo de clase completo. Si Base es una base inaccesible, ambigua o virtual de Derived o es una base de alguna base virtual intermedia de Derived , el programa está mal formado.

  • Si Derived no contiene el miembro original y no es una clase base de la clase que contiene el miembro original, el comportamiento es indefinido.
  • De lo contrario, el puntero resultante puede ser desreferenciado con un objeto Derived , y accederá al miembro dentro del subobjeto base Base de ese objeto Derived .

Conversiones booleanas

Un prvalue de tipos enteros, de punto flotante, enumeración sin ámbito, puntero y puntero-a-miembro puede convertirse en un prvalue de tipo bool .

El valor cero (para tipos integrales, de punto flotante y enumeraciones no delimitadas) y los valores de puntero nulo y puntero-a-miembro nulo se convierten en false . Todos los demás valores se convierten en true .

En el contexto de una inicialización directa , un objeto bool puede ser inicializado desde un prvalue de tipo std::nullptr_t , incluyendo nullptr . El valor resultante es false . Sin embargo, esto no se considera una conversión implícita.

(desde C++11)

Conversiones de calificación

En términos generales:

  • Un prvalue de tipo puntero a tipo calificado-cv T puede convertirse en un prvalue puntero al mismo tipo T más calificado-cv (en otras palabras, se puede añadir constancia y volatilidad).
  • Un prvalue de tipo puntero a miembro de tipo calificado-cv T en clase X puede convertirse en un prvalue puntero a miembro de tipo más calificado-cv T en clase X .

La definición formal de "qualification conversion" se proporciona a continuación .

Tipos similares

Informalmente, dos tipos son similares si, ignorando la calificación cv de nivel superior:

  • son del mismo tipo; o
  • ambos son punteros, y los tipos apuntados son similares; o
  • ambos son punteros a miembro de la misma clase, y los tipos de los miembros apuntados son similares; o
  • ambos son arreglos y los tipos de elementos del arreglo son similares.

Por ejemplo:

  • const int * const * y int ** son similares;
  • int ( * ) ( int * ) y int ( * ) ( const int * ) no son similares;
  • const int ( * ) ( int * ) y int ( * ) ( int * ) no son similares;
  • int ( * ) ( int * const ) y int ( * ) ( int * ) son similares (son el mismo tipo);
  • std:: pair < int , int > y std:: pair < const int , int > no son similares.

Formalmente, la similitud de tipos se define en términos de descomposición de calificación.

Una descomposición de calificaciones de un tipo T es una secuencia de componentes cv_i y P_i tal que T es " cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U " para n no negativo, donde

  • cada cv_i es un conjunto de const y volatile , y
  • cada P_i es
  • “puntero a”,
  • “puntero a miembro de la clase C_i de tipo”,
  • “arreglo de N_i ”, o
  • “arreglo de límite desconocido de”.

Si P_i designa un array, los calificadores cv cv_i+1 en el tipo del elemento también se toman como los calificadores cv cv_i del array.

// T es "puntero a puntero a const int", tiene 3 descomposiciones de calificación:
// n = 0 -> cv_0 está vacío, U es "puntero a puntero a const int"
// n = 1 -> cv_0 está vacío, P_0 es "puntero a",
//          cv_1 está vacío, U es "puntero a const int"
// n = 2 -> cv_0 está vacío, P_0 es "puntero a",
//          cv_1 está vacío, P_1 es "puntero a",
//          cv_2 es "const", U es "int"
using T = const int**;
// sustituir cualquiera de los siguientes tipos en U da una de las descomposiciones:
// U = U0 -> la descomposición con n = 0: U0
// U = U1 -> la descomposición con n = 1: puntero a [U1]
// U = U2 -> la descomposición con n = 2: puntero a [puntero a [const U2]]
using U2 = int;
using U1 = const U2*;
using U0 = U1*;

Dos tipos T1 y T2 son similares si existe una descomposición de calificación para cada uno de ellos, donde se satisfacen todas las siguientes condiciones para las dos descomposiciones de calificación:

  • Tienen el mismo n .
  • Los tipos denotados por U son los mismos.
  • Los correspondientes componentes P_i son los mismos o uno es "array de N_i " y el otro es "array de límite desconocido de" (desde C++20) para todo i .
// la descomposición de calificaciones con n = 2:
// puntero a [puntero volátil a [const int]]
using T1 = const int* volatile *;
// la descomposición de calificaciones con n = 2:
// puntero const a [puntero a [int]]
using T2 = int** const;
// Para las dos descomposiciones de calificaciones anteriores
// aunque cv_0, cv_1 y cv_2 son todos diferentes,
// tienen el mismo n, U, P_0 y P_1,
// por lo tanto los tipos T1 y T2 son similares.

Combinación de calificadores cv

En la descripción a continuación, la descomposición de calificación más larga del tipo Tn se denota como Dn , y sus componentes se denotan como cvn_i y Pn_i .

Una expresión prvalue de tipo T1 puede convertirse al tipo T2 si se cumplen todas las siguientes condiciones:

  • T1 y T2 son similares.
  • Para cada i distinto de cero, si const está en cv1_i , entonces const también está en cv2_i , y de manera similar para volatile .
  • Para cada i distinto de cero, si cv1_i y cv2_i son diferentes, entonces const está en cv2_k para cada k en [ 1 , i ) .

El tipo de calificación combinada de dos tipos T1 y T2 es un tipo T3 similar a T1 tal que

  • cv3_0 está vacío,
  • para cada i distinto de cero, cv3_i es la unión de cv1_i y cv2_i , y
  • si cv3_i es diferente de cv1_i o c2_i , entonces const se añade a cv3_k para cada k en [ 1 , i ) .
(hasta C++20)

El tipo de calificación combinada de dos tipos T1 y T2 es un tipo T3 similar a T1 , donde D3 cumple todas las siguientes condiciones:

  • cv3_0 está vacío.
  • Para cada i distinto de cero, cv3_i es la unión de cv1_i y cv2_i .
  • Si P1_i o P2_i es "array de límite desconocido de", P3_i es "array de límite desconocido de", de lo contrario es P1_i .
  • Si cv3_i es diferente de cv1_i o cv2_i , o P3_i es diferente de P1_i o P2_i , entonces const se añade a cv3_k para cada k en [ 1 , i ) .

Un prvalue de tipo T1 puede convertirse al tipo T2 si el tipo de calificación combinada de T1 y T2 es T2 sin calificación cv.

(desde C++20)
// descomposición de calificación más larga de T1 (n = 2):
// puntero a [puntero a [char]]
using T1 = char**;
// descomposición de calificación más larga de T2 (n = 2):
// puntero a [puntero a [const char]]
using T2 = const char**;
// Determinando los componentes cv3_i y T_i de D3 (n = 2):
// cv3_1 = vacío (unión de cv1_1 vacío y cv2_1 vacío)
// cv3_2 = "const" (unión de cv1_2 vacío y "const" cv2_2)
// P3_0 = "puntero a" (sin array de límite desconocido, usar P1_0)
// P3_1 = "puntero a" (sin array de límite desconocido, usar P1_1)
// Todos los componentes excepto cv_2 son iguales, cv3_2 es diferente de cv1_2,
// por lo tanto agregar "const" a cv3_k para cada k en [1, 2): cv3_1 se convierte en "const".
// T3 es "puntero a puntero const a const char", es decir, const char* const *.
using T3 = /* el tipo combinado por calificación de T1 y T2 */;
int main()
{
    const char c = 'c';
    char* pc;
    T1 ppc = &pc;
    T2 pcc = ppc; // Error: T3 no es igual a T2 sin calificación cv,
                  //        no hay conversión implícita.
    *pcc = &c;
    *pc = 'C';    // Si se permite la asignación errónea anterior,
                  // el objeto const "c" podría ser modificado.
}

Tenga en cuenta que en el lenguaje de programación C, const / volatile solo puede agregarse al primer nivel:

char** p = 0;
char * const* p1 = p;       // Correcto en C y C++
const char* const * p2 = p; // error en C, correcto en C++

Conversiones de punteros a función

  • Un prvalue de tipo puntero a función no lanzadora puede convertirse en un prvalue de puntero a función potencialmente lanzadora.
  • Un prvalue de tipo puntero a función miembro no lanzadora puede convertirse en un prvalue de puntero a función miembro potencialmente lanzadora.
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
(desde C++17)

El problema del booleano seguro

Hasta C++11, diseñar una clase que debiera ser utilizable en contextos booleanos (por ejemplo, if ( obj ) { ... } ) presentaba un problema: dada una función de conversión definida por el usuario, como T :: operator bool ( ) const ; , la secuencia de conversión implícita permitía una secuencia de conversión estándar adicional después de esa llamada a función, lo que significa que el bool resultante podría convertirse a int , permitiendo código como obj << 1 ; o int i = obj ; .

Una solución temprana para esto puede verse en std::basic_ios , que inicialmente define operator void * , de modo que código como if ( std:: cin ) { ... } compila porque void * es convertible a bool , pero int n = std:: cout ; no compila porque void * no es convertible a int . Esto todavía permite que código sin sentido como delete std:: cout ; pueda compilar.

Muchas bibliotecas de terceros anteriores a C++11 fueron diseñadas con una solución más elaborada, conocida como el Safe Bool idiom . std::basic_ios también permitía este idiom mediante LWG issue 468 , y operator void * fue reemplazado (ver notas ).

Desde C++11, la conversión explícita a bool también puede utilizarse para resolver el problema del bool seguro.

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 170 C++98 el comportamiento de las conversiones de puntero-a-miembro no estaba claro
si la clase derivada no tiene el miembro original
aclarado
CWG 172 C++98 el tipo de enumeración se promovía basándose en su tipo subyacente basándose en su rango de valores en su lugar
CWG 330
( N4261 )
C++98 la conversión de double * const ( * p ) [ 3 ]
a double const * const ( * p ) [ 3 ] era inválida
se hizo válida
CWG 519 C++98 los valores de puntero nulo no estaban garantizados de
preservarse al convertir a otro tipo de puntero
siempre preservados
CWG 616 C++98 el comportamiento de la conversión de lvalue a rvalue de
cualquier objeto no inicializado y objetos puntero
con valores inválidos siempre estaba indefinido
se permite el unsigned char
indeterminado; el uso de punteros inválidos
está definido por la implementación
CWG 685 C++98 el tipo subyacente de un tipo de enumeración no era
priorizado en la promoción integral si está fijo
priorizado
CWG 707 C++98 la conversión de entero a punto flotante
tenía comportamiento definido en todos los casos
el comportamiento es indefinido si
el valor que se convierte está
fuera del rango de destino
CWG 1423 C++11 std::nullptr_t era convertible a bool
tanto en inicialización directa como en inicialización por copia
solo inicialización directa
CWG 1773 C++11 una expresión de nombre que aparece en una expresión potencialmente evaluada
tal que el objeto nombrado no es odr-used podría
aún ser evaluado durante una conversión lvalue-to-rvalue
no evaluado
CWG 1781 C++11 std::nullptr_t a bool se consideraba una conversión implícita
aunque solo es válida para inicialización directa
ya no se considera
una conversión implícita
CWG 1787 C++98 el comportamiento de leer desde un valor indeterminado
unsigned char almacenado en caché en un registro no estaba definido
se definió correctamente
CWG 1981 C++11 conversiones contextuales consideradas funciones de conversión explícitas no considerado
CWG 2140 C++11 no estaba claro si las conversiones de lvalue a rvalue desde
std::nullptr_t lvalues obtienen estos lvalues de la memoria
no se obtienen
CWG 2310 C++98 para conversiones de puntero de derivado-a-base y
conversiones de puntero-a-miembro de base-a-derivado,
el tipo de clase derivada podría estar incompleto
debe estar completo
CWG 2484 C++20 char8_t y char16_t tenían diferentes estrategias de
promoción integral, pero pueden acomodar ambas
char8_t debería promocionarse
de la misma manera que char16_t
CWG 2485 C++98 las promociones integrales que involucran campos de bits no estaban bien especificadas se mejoró la especificación
CWG 2813 C++23 la materialización temporal ocurriría cuando se invoca una
función miembro de objeto explícito de un prvalue de clase
no ocurrirá
en este caso
CWG 2861 C++98 un puntero a un objeto de acceso restringido por tipo podría ser
convertido a un puntero a un subobjeto de clase base
el comportamiento es
indefinido en este caso
CWG 2879 C++17 la conversión de materialización temporal se aplicó en prvalue
como operando de un operador que espera glvalue
no aplicado en algunos casos
CWG 2899 C++98 las conversiones de lvalue a rvalue podrían aplicarse a lvalues
que designan objetos con representaciones de valor inválidas
el comportamiento es
indefinido en este caso
CWG 2901 C++98 el resultado de la conversión de lvalue a rvalue desde un unsigned int
lvalue que se refiere a un objeto int con valor - 1 era poco claro
aclarado

Véase también

Documentación de C para Conversiones implícitas