c++ - ¿Por qué estos dos fragmentos de código tienen el mismo efecto?
c++17 decltype (3)
Debido a que el tipo devuelto por un operador ternario se decide de acuerdo con los tipos del segundo y tercer argumento, no de acuerdo con el valor del primero.
Puedes verificar esto con el siguiente código
#include <type_traits>
int main ()
{
auto x = true ? 1 : 2l;
static_assert( std::is_same<decltype(x), long>::value, "!" );
}
¿No es importante eso
true ? 1 : 2l
true ? 1 : 2l
regreso alguna vez
1
;
el operador ternario devuelve un tipo común entre
1
(
int
) y
2l
(
long
).
Eso es
long
En otras palabras: no hay (por el momento) un operador
constexpr
.
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b);
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(true?a:b);
No entiendo por qué estos dos fragmentos de código pueden tener el mismo efecto. Por favor, dame una pista y una explicación subyacente.
Salud.
El tipo de una expresión condicional no depende de si la condición es verdadera o no.
decltype(b<a?a:b)
es el tipo de expresión
b<a?a:b
, que siempre es la misma.
No es
decltype(a)
o
decltype(b)
dependiendo del valor de
b<a
.
Tenga en cuenta que la expresión que le da a
decltype
nunca se evalúa: solo se determina su tipo y se determina en el momento de la compilación.
Algo informal:
-
si
a
se puede convertir al tipo deb
, la expresión tiene el mismo tipo queb
-
si
b
se puede convertir al tipo dea
, la expresión tiene el mismo tipo quea
- de lo contrario, la expresión está mal escrita.
(También hay una gran cantidad de detalles esenciales sobre conversiones estándar y yada-yada involucradas, pero esto es lo esencial).
Por ejemplo, esto no se compilará porque no hay conversiones válidas que podrían dar un
notgood
tipo:
int main()
{
decltype(true ? "hello" : 123.4) notgood;
}
mientras que esto compilará, ejecutará y estará bien definido, porque la desreferencia no válida nunca se evalúa:
int main()
{
decltype(*(int*)0 + 1)` x = 0;
return x;
}
Las reglas para determinar el tipo de una expresión condicional se describen here .
Como los otros ya han dicho, la clave es darse cuenta de que la expresión
E1 ? E2 : E3
tomado como un todo es una expresión , y una expresión tiene un solo tipo (y categoría de valor) determinada en tiempo de compilación. No puede cambiar el tipo según la ruta que se tome, porque en general eso no se conoce hasta el tiempo de ejecución.
Entonces, las reglas son bastante extensas.
Omitiendo los casos especiales
void
y de campo de bits, funciona de la siguiente manera:
-
Si E2 o E3 tienen tipo
void
... suponga que no. - De lo contrario, si E2 o E3 son campos de bits glvalue ... suponga que no lo son.
-
De lo contrario, si E2 y E3 tienen tipos diferentes, al menos uno de los cuales es un tipo de clase (posiblemente calificado por cv) ...
OK, este podría ser cierto. Todavía no conocemos los tipos de E1 y E2, pero ciertamente es plausible.
Si se aplica este caso, hay una lista completa de pasos que debe seguir, y si tiene éxito, descubrió cómo convertir E1 a E2 o E2 a E1. Cualquiera que sea, retomamos en el siguiente paso con dos subexpresiones del mismo tipo.
-
Si E2 y E3 son valores del mismo tipo y la misma categoría de valor, entonces el resultado tiene el mismo tipo y categoría de valor
Es decir, si nuestros T1 y T2 originales son iguales, entonces el tipo de expresión es solo eso. Este es el caso más simple.
Si son tipos diferentes pero el compilador descubrió una conversión implícita en el paso 3 anterior, estamos viendo
(T1,T1)
o(T2,T2)
y lo mismo se aplica. -
De lo contrario, el resultado es un prvalue [aproximadamente - anónimo temporal]. Si E2 y E3 no tienen el mismo tipo, y tienen el tipo de clase (posiblemente calificado por cv), la resolución de sobrecarga se realiza utilizando los candidatos incorporados a continuación para intentar convertir los operandos en tipos incorporados ... los operandos se usan en lugar de los operandos originales para el paso 6
Tal vez son clases con operadores de conversión como
operator bool
, entonces no hemos encontrado otra respuesta, por lo que haremos la conversión abool
y continuaremos. -
Las conversiones lvalue-to-rvalue, array-to-pointer y function-to-pointer se aplican al segundo y tercer operandos
Estas son un montón de conversiones implícitas estándar solo para hacer que ambos lados sean lo más similares posible.
Entonces,
-
Si E2 y E3 ahora tienen el mismo tipo, el resultado es un valor de ese tipo
Nos las arreglamos para masajear ambos lados para tener el mismo tipo, ¡hurra!
-
Si tanto E2 como E3 tienen un tipo aritmético o de enumeración: las conversiones aritméticas habituales se aplican para llevarlas al tipo común, y ese tipo es el resultado
Las conversiones aritméticas habituales son las que le permiten agregar, decir e
int
y undouble
y obtener algún resultado. Esto funcionará de la misma manera. -
etcétera etcétera.
-