Namespaces
Variants

Implicit conversions

From cppreference.net

Cuando una expresión se utiliza en un contexto donde se espera un valor de un tipo diferente, conversión puede ocurrir:

int n = 1L; // la expresión 1L tiene tipo long, se esperaba int
n = 2.1; // la expresión 2.1 tiene tipo double, se esperaba int
char* p = malloc(10); // la expresión malloc(10) tiene tipo void*, se esperaba char*

Las conversiones tienen lugar en las siguientes situaciones:

Contenidos

Conversión como si fuera por asignación

  • En el operador de asignación , el valor del operando derecho se convierte al tipo no calificado del operando izquierdo.
  • En la inicialización escalar , el valor de la expresión inicializadora se convierte al tipo no calificado del objeto que se está inicializando.
  • En una expresión de llamada a función , para una función que tiene un prototipo, el valor de cada expresión de argumento se convierte al tipo de los tipos declarados no calificados del parámetro correspondiente.
  • En una sentencia return , el valor del operando de return se convierte a un objeto que tiene el tipo de retorno de la función.

Tenga en cuenta que la asignación real, además de la conversión, también elimina el rango y precisión adicionales de los tipos de punto flotante y prohíbe superposiciones; esas características no se aplican a la conversión como si fuera por asignación.

Promociones predeterminadas de argumentos

En una expresión de llamada a función cuando la llamada se realiza a

1) una función sin prototipo (hasta C23) ,
2) una variadic function , donde la expresión de argumento es uno de los argumentos finales que coinciden con el parámetro de elipsis.

Cada argumento de tipo entero sufre promoción de enteros , y cada argumento de tipo float se convierte implícitamente al tipo double .

int add_nums(int count, ...);
int sum = add_nums(2, 'c', true); // add_nums se llama con tres ints: (2, 99, 1)

Nótese que float complex e float imaginary no son promovidos a double complex e double imaginary en este contexto.

(desde C99)

Conversiones aritméticas usuales

Los argumentos de los siguientes operadores aritméticos sufren conversiones implícitas con el propósito de obtener el tipo real común  , que es el tipo en el que se realiza el cálculo:

1) Si un operando tiene tipo de coma flotante decimal, el otro operando no debe tener tipo de coma flotante estándar,

complejo o imaginario.

  • Primero, si el tipo de cualquiera de los operandos es _Decimal128 , el otro operando se convierte a _Decimal128 .
  • En caso contrario, si el tipo de cualquiera de los operandos es _Decimal64 , el otro operando se convierte a _Decimal64 .
  • En caso contrario, si el tipo de cualquiera de los operandos es _Decimal32 , el otro operando se convierte a _Decimal32 .
(desde C23)
2) De lo contrario, si un operando es long double , long double complex , o long double imaginary (desde C99) , el otro operando se convierte implícitamente de la siguiente manera:
  • tipo entero o de coma flotante real a long double
(desde C99)
3) De lo contrario, si un operando es double , double complex , o double imaginary (desde C99) , el otro operando se convierte implícitamente de la siguiente manera:
  • tipo entero o de coma flotante real a double
(desde C99)
4) De lo contrario, si un operando es float , float complex , o float imaginary (desde C99) , el otro operando se convierte implícitamente de la siguiente manera:
  • tipo entero a float (el único tipo real posible es float, que permanece como está)
(desde C99)
5) De lo contrario, ambos operandos son enteros. Ambos operandos experimentan promociones enteras ; luego, después de la promoción entera, se aplica uno de los siguientes casos:
  • Si los tipos son iguales, ese tipo es el tipo común.
  • De lo contrario, los tipos son diferentes:
    • Si los tipos tienen la misma signatura (ambos con signo o ambos sin signo), el operando cuyo tipo tiene el menor rango de conversión  [1] se convierte implícitamente [2] al otro tipo.
    • De lo contrario, los operandos tienen signatura diferente:
      • Si el tipo sin signo tiene rango de conversión mayor o igual que el rango del tipo con signo, entonces el operando con tipo con signo se convierte implícitamente al tipo sin signo.
      • De lo contrario, el tipo sin signo tiene rango de conversión menor que el tipo con signo:
        • Si el tipo con signo puede representar todos los valores del tipo sin signo, entonces el operando con tipo sin signo se convierte implícitamente al tipo con signo.
        • De lo contrario, ambos operandos experimentan conversión implícita al tipo sin signo equivalente del tipo del operando con signo.
  1. Consulte promociones enteras a continuación para las reglas sobre rangos.
  2. Consulte "conversiones enteras" en semántica de conversión implícita a continuación.
1.f + 20000001; // int se convierte a float, dando 20000000.00
                // la suma y luego el redondeo a float da 20000000.00
(char)'a' + 1L; // primero, char 'a', que es 97, se promueve a int
                // tipos diferentes: int y long
                // misma signatura: ambos con signo
                // rango diferente: long tiene mayor rango que int
                // por lo tanto, int 97 se convierte a long 97
                // el resultado es 97 + 1 = 98 de tipo signed long
2u - 10; // tipos diferentes: unsigned int y signed int
         // signatura diferente
         // mismo rango
         // por lo tanto, signed int 10 se convierte a unsigned int 10
         // dado que la operación aritmética se realiza para enteros sin signo
         // (ver el tema "Operadores aritméticos"), el cálculo realizado es (2 - 10)
         // módulo (2 elevado a n), donde n es el número de bits de valor de unsigned int
         // si unsigned int es de 32 bits y no hay bits de relleno en su representación
         // de objeto, entonces el resultado es (-8) módulo (2 elevado a 32) = 4294967288
         // de tipo unsigned int
5UL - 2ULL; // tipos diferentes: unsigned long y unsigned long long
            // misma signatura
            // rango diferente: el rango de unsigned long long es mayor
            // por lo tanto, unsigned long 5 se convierte a unsigned long long 5
            // dado que la operación aritmética se realiza para enteros sin signo
            // (ver el tema "Operadores aritméticos"),
            // si unsigned long long es de 64 bits, entonces
            // el resultado es (5 - 2) módulo (2 elevado a 64) = 3 de tipo
            // unsigned long long
0UL - 1LL; // tipos diferentes: unsigned long y signed long long
           // signatura diferente
           // rango diferente: el rango de signed long long es mayor.
           // si ULONG_MAX > LLONG_MAX, entonces signed long long no puede representar todos
           // los unsigned long por lo tanto, este es el último caso: ambos operandos se convierten
           // a unsigned long long unsigned long 0 se convierte a unsigned long long 0
           // long long 1 se convierte a unsigned long long 1 dado que la operación
           // aritmética se realiza para enteros sin signo
           // (ver el tema "Operadores aritméticos"),
           // si unsigned long long es de 64 bits, entonces
           // el cálculo es (0 - 1) módulo (2 elevado a 64)
           // así, el resultado es 18446744073709551615 (ULLONG_MAX) de tipo
           // unsigned long long

El tipo de resultado se determina de la siguiente manera:

  • si ambos operandos son complejos, el tipo de resultado es complejo;
  • si ambos operandos son imaginarios, el tipo de resultado es imaginario;
  • si ambos operandos son reales, el tipo de resultado es real;
  • si los dos operandos de punto flotante tienen dominios de tipo diferentes (complejo vs real, complejo vs imaginario, o imaginario vs real), el tipo de resultado es complejo.
double complex z = 1 + 2*I;
double f = 3.0;
z + f; // z permanece como está, f se convierte a double, el resultado es double complex
(desde C99)

Como siempre, el resultado de un operador de punto flotante puede tener mayor rango y precisión de lo que indica su tipo (ver FLT_EVAL_METHOD ).

Nota: los operandos reales e imaginarios no se convierten implícitamente a complejos porque hacerlo requeriría cálculos adicionales, mientras que produciría resultados indeseables en ciertos casos que involucran infinitos, NaNs y ceros con signo. Por ejemplo, si los reales se convirtieran a complejos, 2.0×(3.0+i∞) se evaluaría como (2.0+i0.0)×(3.0+i∞) ⇒ (2.0×3.0–0.0×∞) + i(2.0×∞+0.0×3.0) ⇒ NaN+i∞ en lugar del correcto 6.0+i∞. Si los imaginarios se convirtieran a complejos, i2.0×(∞+i3.0) se evaluaría como (0.0+i2.0) × (∞+i3.0) ⇒ (0.0×∞ – 2.0×3.0) + i(0.0×3.0 + 2.0×∞) ⇒ NaN + i∞ en lugar de –6.0 + i∞.

(desde C99)

Nota: independientemente de las conversiones aritméticas habituales, el cálculo siempre puede realizarse en un tipo más estrecho que el especificado por estas reglas bajo la regla as-if .

Transformaciones de valores

Conversión de lvalue

Cualquier expresión lvalue de cualquier tipo no-array, cuando se utiliza en cualquier contexto excepto

sufre conversión de lvalue  : el tipo permanece igual, pero pierde const / volatile / restrict -calificadores y atomic propiedades, si las tiene. El valor permanece igual, pero pierde sus propiedades de lvalue (la dirección ya no puede ser tomada).

Si el lvalue tiene tipo incompleto, el comportamiento es indefinido.

Si el valor-l designa un objeto de duración de almacenamiento automático cuya dirección nunca fue tomada y si ese objeto no fue inicializado (no declarado con un inicializador y no se realizó ninguna asignación a él antes de su uso), el comportamiento es indefinido.

Esta conversión modela la carga en memoria del valor del objeto desde su ubicación.

volatile int n = 1;
int x = n;            // la conversión de lvalue en n lee el valor de n
volatile int* p = &n; // no hay conversión de lvalue: no lee el valor de n

Conversión de array a puntero

Cualquier expresión lvalue (until C99) expresión (since C99) de tipo array , cuando se utiliza en cualquier contexto excepto

sufre una conversión al puntero no lvalue a su primer elemento.

Si el array fue declarado register , el comportamiento es indefinido.

Un array que no es un lvalue, o cualquiera de sus elementos , no es accesible (until C99) , tiene tiempo de vida temporal (since C99) .

int a[3], b[3][4];
int* p = a;      /* conversión a &a[0] */
int (*q)[4] = b; /* conversión a &b[0] */
struct S
{
    int a[1];
};
struct S f(void)
{
    struct S result = {{0}}; /* {0} desde C99 */
    return result;
}
void g(void)
{
    int* p = f().a;    /* error hasta C99; OK desde C99 */
    int n  = f().a[0]; /* error hasta C99; OK desde C99 */
    f().a[0] = 13;     /* error hasta C99; UB desde C99 */
    (void)p, (void)n;
}
int main(void) { return 0; }

Conversión de función a puntero

Cualquier expresión designadora de función, cuando se utiliza en cualquier contexto que no sea

sufre una conversión al puntero no lvalue a la función designada por la expresión.

int f(int);
int (*p)(int) = f; // conversión a &f
(***p)(1); // desreferenciación repetida a f y conversión de vuelta a &f

Semántica de conversión implícita

La conversión implícita, ya sea como si fuera por asignación  o una conversión aritmética usual  , consiste en dos etapas:

1) transformación de valor (si es aplicable),
2) una de las conversiones listadas a continuación (si puede producir el tipo destino).

Tipos compatibles

La conversión de un valor de cualquier tipo a cualquier tipo compatible es siempre una operación nula y no cambia la representación.

uint8_t (*a)[10];         // si uint8_t es un typedef para unsigned char
unsigned char (*b)[] = a; // entonces estos tipos de puntero son compatibles

Promociones de enteros

La promoción de enteros es la conversión implícita de un valor de cualquier tipo entero con rango menor o igual al rango de int o de un campo de bits de tipo _Bool (until C23) bool (since C23) , int , signed int , unsigned int , al valor de tipo int o unsigned int .

Si int puede representar el rango completo de valores del tipo original (o el rango de valores del campo de bits original), el valor se convierte al tipo int . De lo contrario, el valor se convierte a unsigned int .

El valor de un campo de bits de un tipo entero de precisión de bits se convierte al tipo entero de precisión de bits correspondiente. De lo contrario, los tipos enteros de precisión de bits están exentos de las reglas de promoción de enteros.

(desde C23)

Las promociones enteras preservan el valor, incluido el signo:

int main(void)
{
    void f(); // declaración de función al estilo antiguo
              // desde C23, void f(...) tiene el mismo comportamiento respecto a promociones
    char x = 'a'; // conversión de entero de int a char
    f(x); // promoción de entero de char de vuelta a int
}
void f(x) int x; {} // la función espera int

rank anterior es una propiedad de todo tipo entero y se define de la siguiente manera:

1) los rangos de todos los tipos enteros con signo son diferentes y aumentan con su precisión: rango de signed char < rango de short < rango de int < rango de long int < rango de long long int
2) los rangos de todos los tipos enteros con signo son iguales a los rangos de los tipos enteros sin signo correspondientes
3) el rango de cualquier tipo entero estándar es mayor que el rango de cualquier tipo entero extendido o tipo entero de precisión de bits (desde C23) del mismo tamaño (es decir, el rango de __int64 < rango de long long int , pero el rango de long long < rango de __int128 debido a la regla (1) )
4) el rango de char es igual al rango de signed char y al rango de unsigned char
5) el rango de _Bool (until C23) bool (since C23) es menor que el rango de cualquier otro tipo entero estándar
6) el rango de cualquier tipo enumerado es igual al rango de su tipo entero compatible
7) el ranking es transitivo: si el rango de T1 < rango de T2 y el rango de T2 < rango de T3 entonces el rango de T1 < rango de T3.
8) el rango de un tipo entero con signo de precisión de bits será mayor que el rango de cualquier tipo entero estándar con menor anchura o cualquier tipo entero de precisión de bits con menor anchura.
9) el rango de cualquier tipo entero de precisión de bits relativo a un tipo entero extendido de la misma anchura está definido por la implementación.
(desde C23)
10) cualquier aspecto de la clasificación relativa de los tipos enteros extendidos no cubierto anteriormente está definido por la implementación.

Nota: las promociones de enteros se aplican únicamente

  • como parte de las conversiones aritméticas usuales (ver arriba),
  • como parte de las promociones de argumentos por defecto (ver arriba),
  • al operando de los operadores aritméticos unarios + y - ,
  • al operando del operador unario de bits ~ ,
  • a ambos operandos de los operadores de desplazamiento << y >> .

Conversión booleana

Un valor de cualquier tipo escalar puede convertirse implícitamente a _Bool (hasta C23) bool (desde C23) . Los valores que comparan iguales a una expresión constante entera de valor cero (hasta C23) son cero (para tipos aritméticos), nulos (para tipos puntero) o tienen tipo nullptr_t (desde C23) se convierten a 0 (hasta C23) false (desde C23) , todos los demás valores se convierten a 1 (hasta C23) true (desde C23) .

bool b1 = 0.5;              // b1 == 1 (0.5 converted to int would be zero)
bool b2 = 2.0*_Imaginary_I; // b2 == 1 (but converted to int would be zero)
bool b3 = 0.0 + 3.0*I;      // b3 == 1 (but converted to int would be zero)
bool b4 = 0.0 / 0.0;        // b4 == 1 (NaN does not compare equal to zero)
bool b5 = nullptr;          // b5 == 0 (since C23: nullptr is converted to false)
(desde C99)

Conversiones de enteros

Un valor de cualquier tipo entero puede convertirse implícitamente a cualquier otro tipo entero. Excepto cuando esté cubierto por las promociones y conversiones booleanas mencionadas anteriormente, las reglas son:

  • si el tipo destino puede representar el valor, el valor permanece sin cambios,
  • de lo contrario, si el tipo destino es sin signo, el valor 2 b
    , donde b es el número de bits de valor en el tipo destino, se resta o suma repetidamente al valor origen hasta que el resultado quepa en el tipo destino. En otras palabras, los enteros sin signo implementan aritmética modular.
  • de lo contrario, si el tipo destino es con signo, el comportamiento está definido por la implementación (lo cual puede incluir la generación de una señal).
char x = 'a'; // int -> char, resultado sin cambios
unsigned char n = -123456; // objetivo es unsigned, resultado es 192 (es decir, -123456+483*256)
signed char m = 123456;    // objetivo es signed, resultado está definido por la implementación
assert(sizeof(int) > -1);  // assert falla:
                           // operador > solicita conversión de -1 a size_t,
                           // objetivo es unsigned, resultado es SIZE_MAX

Conversiones de coma flotante a entero real

Un valor finito de cualquier tipo de punto flotante real puede convertirse implícitamente a cualquier tipo entero. Excepto cuando está cubierto por la conversión booleana anterior, las reglas son:

  • La parte fraccionaria se descarta (truncada hacia cero).
  • Si el valor resultante puede ser representado por el tipo de destino, se utiliza ese valor
  • de lo contrario, el comportamiento no está definido.
int n = 3.14; // n == 3
int x = 1e10; // comportamiento indefinido para int de 32 bits

Un valor de cualquier tipo entero puede convertirse implícitamente a cualquier tipo de coma flotante real.

  • si el valor puede representarse exactamente por el tipo de destino, permanece sin cambios.
  • si el valor puede representarse, pero no de forma exacta, el resultado es una elección definida por la implementación del valor más cercano superior o inferior, aunque si se admite aritmética IEEE, el redondeo es al más cercano. No está especificado si FE_INEXACT se activa en este caso.
  • si el valor no puede representarse, el comportamiento es indefinido, aunque si se admite aritmética IEEE, FE_INVALID se activa y el valor resultante no está especificado.

El resultado de esta conversión puede tener un rango y precisión mayores de lo que indica su tipo de destino (consulte FLT_EVAL_METHOD ).

Si se necesita control sobre FE_INEXACT en conversiones de punto flotante a entero, rint y nearbyint pueden utilizarse.

double d = 10; // d = 10.00
float f = 20000001; // f = 20000000.00 (FE_INEXACT)
float x = 1 + (long long)FLT_MAX; // comportamiento indefinido

Conversiones de números reales en punto flotante

Un valor de cualquier tipo real de punto flotante puede convertirse implícitamente a cualquier otro tipo real de punto flotante.

  • Si el valor puede ser representado exactamente por el tipo destino, permanece sin cambios.
  • Si el valor puede ser representado, pero no de forma exacta, el resultado es el valor más cercano superior o el más cercano inferior (en otras palabras, la dirección del redondeo está definida por la implementación), aunque si se soporta aritmética IEEE, el redondeo es al más cercano.
  • Si el valor no puede ser representado, el comportamiento es indefinido .

El resultado de esta conversión puede tener mayor rango y precisión de lo que indica su tipo de destino (consulte FLT_EVAL_METHOD ).

double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625
float f = d;    // f = 0.100000001490116119384765625
float x = 2 * (double)FLT_MAX; // indefinido

Conversiones de tipos complejos

Un valor de cualquier tipo complejo puede convertirse implícitamente a cualquier otro tipo complejo. La parte real y la parte imaginaria siguen individualmente las reglas de conversión para los tipos de coma flotante reales.

double complex d = 0.1 + 0.1*I;
float complex f = d; // f es (0.100000001490116119384765625, 0.100000001490116119384765625)

Conversiones de tipos imaginarios

Un valor de cualquier tipo imaginario puede convertirse implícitamente a cualquier otro tipo imaginario. La parte imaginaria sigue las reglas de conversión para los tipos de coma flotante reales.

double imaginary d = 0.1*_Imaginary_I;
float imaginary f = d; // f es 0.100000001490116119384765625*I

Conversiones real-complejo

Un valor de cualquier tipo de coma flotante real puede convertirse implícitamente a cualquier tipo complejo.

  • La parte real del resultado se determina por las reglas de conversión para los tipos de coma flotante reales.
  • La parte imaginaria del resultado es cero positivo (o cero sin signo en sistemas no IEEE).

Un valor de cualquier tipo complejo puede convertirse implícitamente a cualquier tipo de coma flotante real.

  • La parte real se convierte siguiendo las reglas para los tipos de coma flotante reales.
  • La parte imaginaria se descarta.

Nota: en la conversión complejo-real, un NaN en la parte imaginaria no se propagará al resultado real.

double complex z = 0.5 + 3*I;
float f = z;  // la parte imaginaria se descarta, f se establece en 0.5
z = f;        // establece z en 0.5 + 0*I

Conversiones real-imaginario

Un valor de cualquier tipo imaginario puede convertirse implícitamente a cualquier tipo real (entero o coma flotante). El resultado es siempre un cero positivo (o sin signo), excepto cuando el tipo destino es _Bool (hasta C23) bool (desde C23) , en cuyo caso se aplican las reglas de conversión booleana.

Un valor de cualquier tipo real puede convertirse implícitamente a cualquier tipo imaginario. El resultado es siempre un cero imaginario positivo.

double imaginary z = 3*I;
bool b = z;  // Conversión booleana: establece b en true
float f = z; // Conversión real-imaginario: establece f en 0.0
z = 3.14;    // Conversión imaginario-real: establece z en 0*_Imaginary_I

Conversiones complejo-imaginario

Un valor de cualquier tipo imaginario puede convertirse implícitamente a cualquier tipo complejo.

  • La parte real del resultado es el cero positivo.
  • La parte imaginaria del resultado sigue las reglas de conversión para los tipos reales correspondientes.

Un valor de cualquier tipo complejo puede convertirse implícitamente a cualquier tipo imaginario.

  • La parte real se descarta.
  • La parte imaginaria del resultado sigue las reglas de conversión para los tipos reales correspondientes.
double imaginary z = I * (3*I); // el resultado complejo -3.0+0i pierde la parte real
                                // establece z en 0*_Imaginary_I
(desde C99)

Conversiones de punteros

Un puntero a void puede convertirse implícitamente hacia y desde cualquier puntero a tipo objeto con la siguiente semántica:

  • Si un puntero a objeto se convierte a un puntero a void y viceversa, su valor se compara igual al puntero original.
  • No se ofrecen otras garantías.
int* p = malloc(10 * sizeof(int)); // malloc devuelve void*

Un puntero a un tipo no calificado puede convertirse implícitamente al puntero a la versión calificada de ese tipo (en otras palabras, const , volatile , y restrict pueden añadirse). El puntero original y el resultado se comparan iguales.

int n;
const int* p = &n; // &n tiene tipo int*

Cualquier expresión constante entera constante con valor 0 así como cualquier expresión de puntero entero con valor cero convertida al tipo void * puede ser convertida implícitamente a cualquier tipo de puntero (tanto puntero a objeto como puntero a función). El resultado es el valor de puntero nulo de su tipo, garantizado que comparará desigual con cualquier valor de puntero no nulo de ese tipo. Esta expresión entera o void * se conoce como constante de puntero nulo y la biblioteca estándar proporciona una definición de esta constante como la macro NULL .

int* p = 0;
double* q = NULL;

Notas

Aunque el desbordamiento de enteros con signo en cualquier operador aritmético es comportamiento indefinido, desbordar un tipo entero con signo en una conversión de enteros es meramente comportamiento no especificado.

Por otro lado, aunque el desbordamiento de enteros sin signo en cualquier operador aritmético (y en la conversión de enteros) es una operación bien definida y sigue las reglas de la aritmética modular, desbordar un entero sin signo en una conversión de punto flotante a entero es comportamiento indefinido: los valores de tipo de punto flotante real que pueden convertirse a entero sin signo son los valores del intervalo abierto ( -1 , Unnn_MAX + 1 ) .

unsigned int n = -1.0; // comportamiento indefinido

Las conversiones entre punteros y enteros (excepto desde puntero a _Bool (hasta C23) bool (desde C23) y (desde C99) desde una expresión constante entera con valor cero a puntero), entre punteros a objetos (excepto cuando uno de ellos es un puntero a void) y conversiones entre punteros a funciones (excepto cuando las funciones tienen tipos compatibles) nunca son implícitas y requieren un operador de cast .

No existen conversiones (implícitas o explícitas) entre punteros a funciones y punteros a objetos (incluyendo void * ) o enteros.

Referencias

  • Estándar C23 (ISO/IEC 9899:2024):
  • 6.3 Conversiones (p: 44-50)
  • Estándar C17 (ISO/IEC 9899:2018):
  • 6.3 Conversiones (p: 37-41)
  • Estándar C11 (ISO/IEC 9899:2011):
  • 6.3 Conversiones (p: 50-56)
  • Estándar C99 (ISO/IEC 9899:1999):
  • 6.3 Conversiones (p: 42-48)
  • Estándar C89/C90 (ISO/IEC 9899:1990):
  • 3.2 Conversiones

Véase también

Documentación de C++ para Conversiones implícitas