c++ c++17 decltype ternary

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 de b , la expresión tiene el mismo tipo que b
  • si b se puede convertir al tipo de a , la expresión tiene el mismo tipo que a
  • 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:

  1. Si E2 o E3 tienen tipo void ... suponga que no.
  2. De lo contrario, si E2 o E3 son campos de bits glvalue ... suponga que no lo son.
  3. 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.

  4. 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.

  5. 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 a bool y continuaremos.

  6. 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,

    1. 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!

    2. 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 un double y obtener algún resultado. Esto funcionará de la misma manera.

    3. etcétera etcétera.