operator conditionals c++ c++11 conditional-operator

operator - conditionals c++



¿Por qué parece que std:: istringstream se resuelve de manera diferente a std:: ifstream en el operador ternario(?:)? (4)

Ejemplo minimizado:

class A { }; class B : public A { }; class C : public A { }; int main() { B b; C c; A& refA = true? b : c; }

Clang informa:

main.cpp:13:19: error: incompatible operand types (''B'' and ''C'') A& refA = true? b : c;

La regla relevante se encuentra en §5.16 [expr.cond] / p3-6 de la norma:

3 De lo contrario, si el segundo y tercer operando tienen tipos diferentes y tienen un tipo de clase (posiblemente cv calificado), o si ambos son valores de la misma categoría de valor y el mismo tipo, excepto para la calificación de cv, se intenta convertir cada uno de esos operandos al tipo del otro. El proceso para determinar si una expresión de operador E1 de tipo T1 se puede convertir para que coincida con una expresión de funcionamiento E2 de tipo T2 se define de la siguiente manera:

  • Si E2 es un lvalue: E1 se puede convertir para que coincida con E2, si E1 se puede convertir implícitamente (Cláusula 4) al tipo "lvalue reference to T2", sujeto a la restricción de que en la conversión la referencia debe vincularse directamente (8.5.3 ) a un valor.
  • Si E2 es un xvalor: E1 se puede convertir para que coincida con E2, si E1 se puede convertir implícitamente al tipo "rvalue reference to T2", sujeto a la restricción de que la referencia debe vincularse directamente.
  • Si E2 es un prvalue o si no se puede realizar ninguna de las conversiones anteriores y al menos uno de los operandos tiene un tipo de clase (posiblemente cv-calificado):
    • si E1 y E2 tienen un tipo de clase, y los tipos de clase subyacentes son los mismos o uno es una clase base del otro: E1 se puede convertir para que coincida con E2 si la clase de T2 es del mismo tipo, o una clase base de, la clase de T1, y la calificación cv de T2 es la misma calificación cv que, o una calificación cv mayor que, la calificación cv de T1. Si se aplica la conversión, E1 se cambia a un valor predeterminado de tipo T2 al inicializar con copia un temporal de tipo T2 desde E1 y usar ese temporal como el operando convertido.
    • De lo contrario (es decir, si E1 o E2 tienen un tipo que no sea de clase, o si ambos tienen tipos de clase pero las clases subyacentes no son las mismas o una clase base de la otra): E1 se puede convertir para que coincida con E2 si E1 puede ser implícitamente convertido al tipo que tendría la expresión E2 si E2 se convirtiera en un prvalue (o el tipo que tiene, si E2 es un prvalue).

Usando este proceso, se determina si el segundo operando se puede convertir para que coincida con el tercer operando, y si el tercer operando se puede convertir para que coincida con el segundo operando. Si ambos se pueden convertir, o uno se puede convertir pero la conversión es ambigua, el programa está mal formado. Si ninguno de los dos puede convertirse, los operandos se dejan sin cambios y se realiza una verificación adicional como se describe a continuación. Si es posible exactamente una conversión, esa conversión se aplica al operando elegido y el operando convertido se usa en lugar del operando original por el resto de esta sección.

4 Si los segundo y tercer operandos son valores de la misma categoría de valor y tienen el mismo tipo, el resultado es de ese tipo y categoría de valor y es un campo de bits si el segundo o el tercer operando es un campo de bits, o Si ambos son campos de bits.

5 De lo contrario, el resultado es un prvalue. Si el segundo y tercer operandos no tienen el mismo tipo, y cualquiera de los dos tiene un tipo de clase (posiblemente cv calificado), la resolución de sobrecarga se usa para determinar las conversiones (si las hay) que se aplicarán a los operandos (13.3.1.2, 13.6) . Si la resolución de sobrecarga falla, el programa está mal formado. De lo contrario, las conversiones así determinadas se aplican y los operandos convertidos se usan en lugar de los operandos originales para el resto de esta sección.

6 Las conversiones estándar de Lvalue a rvalue (4.1), matriz a puntero (4.2) y función a puntero (4.3) se realizan en los operandos segundo y tercero. Después de esas conversiones, se mantendrá una de las siguientes:

  • El segundo y tercer operandos tienen el mismo tipo; El resultado es de ese tipo. Si los operandos tienen un tipo de clase, el resultado es un prvalor temporal del tipo de resultado, que se inicializa con copia desde el segundo operando o el tercer operando, dependiendo del valor del primer operando.
  • El segundo y tercer operandos tienen aritmética o tipo de enumeración; Las conversiones aritméticas habituales se realizan para llevarlas a un tipo común, y el resultado es de ese tipo.
  • Uno o ambos de los operandos segundo y tercero tienen tipo puntero; Las conversiones de puntero (4.10) y las conversiones de calificación (4.4) se realizan para llevarlas a su tipo de puntero compuesto (Cláusula 5). El resultado es del tipo de puntero compuesto.
  • Uno o ambos de los operandos segundo y tercero tienen puntero al tipo de miembro; puntero a las conversiones de miembros (4.11) y las conversiones de calificación (4.4) se realizan para llevarlos a su tipo de puntero compuesto (Cláusula 5). El resultado es del tipo de puntero compuesto.
  • Tanto el segundo como el tercer operando tienen el tipo std::nullptr_t o uno tiene ese tipo y el otro es una constante de puntero nulo. El resultado es de tipo std::nullptr_t .

El punto crucial es que esto siempre intentará convertir un operando para que coincida con el tipo del otro, en lugar de convertir ambos a un tercer tipo, hasta que llegue al párrafo 5, momento en el que el compilador comienza a buscar conversiones implícitas definidas por el usuario a tipos de puntero o aritméticos (¿esos son solo los posibles argumentos de las funciones candidatas incorporadas para el operator?: definidas en §13.6), y para sus propósitos, realmente no quiere que llegue allí.

En el ejemplo minimizado, que se relaciona directamente con su caso de error ( A = istream , B = ifstream , C = istringstream ), no es posible convertir uno al tipo del otro, por lo que la lógica desciende a p5 y el compilador busca conversiones implícitas definidas por el usuario. En el ejemplo minimizado no hay conversión, falla la resolución de sobrecarga y todo está mal formado. En su caso de error, pre-C ++ 11 (y en libstdc ++ post-C ++ 11, aparentemente) hay una conversión implícita de un flujo a void * , por lo que el compilador hace eso, dando a la expresión completa un tipo void * , pero eso obviamente no puede vincularse a una referencia a std::istream , así que ese es el error que ves.

En tu segundo caso:

ifs.is_open() ? ifs : true ? iss : std::cin;

std::cin tiene el tipo std::istream , y std::istringstream se puede convertir a su clase base std::istream , por lo que la expresión condicional interna está bien formada y tiene el tipo std::istream . Luego, con la expresión condicional externa, nuevamente el tipo del segundo operando, std::ifstream , es convertible al tipo del tercer operando, std::istream , por lo que toda la expresión está bien formada y tiene el tipo correcto para enlazar a la referencia.

Estoy acostumbrado a escribir pequeñas herramientas de línea de comandos que toman un nombre de archivo o leen desde std::cin , así que he estado usando este patrón durante bastante tiempo:

int main(int argc, char* argv[]) { std::string filename; // args processing ... std::ifstream ifs; if(!filename.empty()) ifs.open(filename); std::istream& is = ifs.is_open() ? ifs : std::cin; std::string line; while(std::getline(is, line)) { // process line... } return 0; }

Después de leer una pregunta sobre Stack Overflow, traté de modificar mi patrón habitual para adaptarlo a la necesidad de leer desde un archivo o desde un std::istringstream . Para mi sorpresa no se compila y da este error:

temp.cpp:164:47: error: invalid initialization of non-const reference of type ‘std::istream& {aka std::basic_istream<char>&}’ from an rvalue of type ‘void*’ std::istream& is = ifs.is_open() ? ifs : iss; // won''t compile

Que me parece que está intentando convertir el objeto std::istringstream ( iss ) en un booleano y que su operator void*() .

int main(int argc, char* argv[]) { std::string filename; std::string data; // args processing ... std::ifstream ifs; std::istringstream iss; if(!filename.empty()) ifs.open(filename); std::istream& is = ifs.is_open() ? ifs : iss; // won''t compile std::string line; while(std::getline(is, line)) { // process line... } return 0; }

  1. ¿Por qué trata std::istringstream diferente a std::cin y std::ifstream ? Todos ellos derivan de std::istream .

    Entonces recordé haber convertido mi patrón para acomodar tres posibilidades, leyendo de archivo, cadena o std::cin . Y recuerdo que funcionó (aunque es bastante torpe). Así que aplicando la triple solución a este problema, se me ocurrió un fudge que funciona totalmente:

    int main(int argc, char* argv[]) { std::string filename; std::string data; // args processing ... std::ifstream ifs; std::istringstream iss; if(!filename.empty()) ifs.open(filename); std::istream& is = ifs.is_open() ? ifs : true ? iss : std::cin; // fudge works std::string line; while(std::getline(is, line)) { // process line... } return 0; }

  2. ¿Por qué funciona este fudge? ¿GCC rompe alguna regla sobre cómo el operador ternario ( ?: :) Resuelve sus tipos? ¿O me estoy perdiendo algo?


El compilador intenta encontrar un tipo común para ambos resultados del operador ternario, y si ve, por ejemplo, esta referencia , verá que hay una anulación del operador de conversión para void* (o bool para C ++ 11 y posterior) , por lo que el compilador utiliza eso.

Pero cuando intenta realizar la asignación, se produce un error porque en el lado derecho de la inicialización tiene un tipo void* (alternativamente bool ), y en el lado izquierdo hay una referencia a std::istream .

Para resolverlo, debe convertir manualmente cada flujo a una referencia a std::istream con, por ejemplo, static_cast .


Si tiene una clase base y una clase derivada, el operador condicional ternario sabe cómo convertir la clase derivada en la clase base. Pero si tiene dos clases derivadas, no sabe cómo convertirlas a su clase base común. Esto no es gcc actuando; así es como se especifica el operador condicional ternario para trabajar en el estándar.

std::istream& is = ifs.is_open() ? ifs : std::cin;

Esto funciona bien porque std::cin tiene el tipo std::istream , que es una clase base de std::ifstream .

std::istream& is = ifs.is_open() ? ifs : iss; // won''t compile

Esto no funciona porque std::ifstream y std::istringstream "solo" tienen una clase base común.

std::istream& is = ifs.is_open() ? ifs : true ? iss : std::cin; // fudge works

Esto funciona porque se analiza como:

std::istream& is = ifs.is_open() ? ifs : (true ? iss : std::cin);

y la expresión entre paréntesis tiene el tipo std::istream . De modo que iss se convierte en un lvalue de tipo std::istream , si se selecciona, y ifs también se convierte de manera similar.


gcc se queja porque ifs y iss son dos tipos diferentes. static_casting los tipos a std :: istream y resolverán su problema.