c++ language-lawyer

c++ - Operador de reparto diferente llamado por diferentes compiladores



language-lawyer (4)

Corto

El operator int() función de conversión operator int() se selecciona mediante clang sobre el operator bool() const ya que b no está const, mientras que el operador de conversión para bool es.

El breve razonamiento es que el candidato funciona para la resolución de sobrecarga (con el parámetro de objeto implícito en su lugar), al convertir b en bool son

operator bool (B2 const &); operator int (B2 &);

donde el segundo es una mejor coincidencia ya que b no está const calificado.

Si ambas funciones comparten la misma calificación (ya sea const o no), se selecciona operator bool ya que proporciona conversión directa.

Conversión a través de anotación, analizada paso a paso

Si aceptamos que se llama al boolean ostream inserter (std :: basic_ostream :: operator << (bool val) según [ostream.inserters.arithmetic]) con el valor que resulta de una conversión de b a bool , podemos profundizar en esa conversión.

1. La expresión moldeada

El elenco de b bool

(bool)b

evalúa a

static_cast<bool>(b)

según C ++ 11, 5.4 / 4 [expr.cast] ya que const_cast no es aplicable (no agrega ni elimina const aquí).

Esta conversión estática está permitida por C ++ 11, 5.2.9 / 4 [expr.static.cast] , si bool t(b); para una variable inventada t está bien formada. Tales declaraciones se llaman inicialización directa según C ++ 11, 8.5 / 15 [dcl.init] .

2. Inicialización directa bool t(b);

Cláusula 16 de los estados de párrafo estándar menos mencionados (énfasis mío):

La semántica de los inicializadores es la siguiente. El tipo de destino es el tipo del objeto o referencia que se inicializa y el tipo de fuente es el tipo de expresión del inicializador.

[...]

[...] si el tipo de fuente es un tipo de clase (posiblemente cv calificado) , se consideran las funciones de conversión .

Las funciones de conversión aplicables se enumeran, y la mejor se elige a través de la resolución de sobrecarga.

2.1 ¿Qué funciones de conversión están disponibles?

Las funciones de conversión disponibles son operator int () y operator bool() const ya que C ++ 11, 12.3 / 5 [class.conv] nos dice:

Una función de conversión en una clase derivada no oculta una función de conversión en una clase base a menos que las dos funciones se conviertan al mismo tipo.

Mientras que C ++ 11, 13.3.1.5/1 [over.match.conv] establece:

Se consideran las funciones de conversión de S y sus clases base.

donde S es la clase de la que se convertirá.

2.2 ¿Qué funciones de conversión son aplicables?

C ++ 11, 13.3.1.5/1 [over.match.conv] (énfasis mío):

1 [...] Suponiendo que "cv1 T" es el tipo de objeto que se está inicializando, y "cv S" es el tipo de expresión del inicializador, con S un tipo de clase, las funciones candidatas se seleccionan de la siguiente manera: La conversión funciones de S y sus clases base son consideradas. Las funciones de conversión no explícitas que no están ocultas en S y producen el tipo T o un tipo que se puede convertir en tipo T a través de una secuencia de conversión estándar son funciones candidatas.

Por lo tanto, el operator bool () const es aplicable ya que no está oculto dentro de B2 y produce un bool .

La parte con énfasis en la última cotización estándar es relevante para la conversión utilizando el operator int () ya que int es un tipo que se puede convertir a bool a través de la secuencia de conversión estándar. La conversión de int a bool no es ni siquiera una secuencia, sino una simple conversión directa que está permitida por C ++ 11, 4.12 / 1 [conv.bool]

Un prvalor de enumeración aritmética, sin cobertura, puntero o puntero a tipo de miembro se puede convertir a un prvalue de tipo bool. Un valor cero, un valor de puntero nulo o un valor de puntero de miembro nulo se convierte en falso; cualquier otro valor se convierte en verdadero.

Esto significa que el operator int () es aplicable.

2.3 ¿Qué función de conversión se selecciona?

La selección de la función de conversión apropiada se realiza a través de la resolución de sobrecarga ( C ++ 11, 13.3.1.5/1 [over.match.conv] ):

La resolución de sobrecarga se utiliza para seleccionar la función de conversión que se invocará.

Hay una "peculiaridad" especial cuando se trata de la resolución de sobrecarga para las funciones miembro de la clase: el parámetro de objeto implícito ".

Por C ++ 11, 13.3.1 [over.match.funcs] ,

[...] tanto las funciones miembro estáticas como no estáticas tienen un parámetro de objeto implícito [...]

donde el tipo de este parámetro para las funciones miembro no estático -según la cláusula 4- es:

  • "Lvalue reference to cv X" para las funciones declaradas sin un ref-qualifier o con el calificador & ref

  • "Referencia de rvalue a cv X" para las funciones declaradas con el calificador && ref

donde X es la clase de la cual la función es miembro y cv es la calificación cv en la declaración de la función miembro.

Esto significa que (por C ++ 11, 13.3.1.5/2 [over.match.conv] ), en una función de inicialización por conversión,

[t] la lista de argumentos tiene un argumento, que es la expresión del inicializador. [Nota: este argumento se comparará con el parámetro de objeto implícito de las funciones de conversión. -finalizar nota]

Las funciones candidatas para la resolución de sobrecarga son:

operator bool (B2 const &); operator int (B2 &);

Obviamente, el operator int () es una mejor coincidencia si se solicita una conversión utilizando un objeto no constante de tipo B2 ya que el operator bool () requirió una conversión de calificación.

Si ambas funciones de conversión comparten la misma calificación const, la resolución de sobrecarga de esas funciones ya no servirá. En este caso, la clasificación de conversión (secuencia) entra en su lugar.

3. ¿Por qué se selecciona operator bool () cuando ambas funciones de conversión comparten la misma calificación const?

La conversión de B2 a bool es una secuencia de conversión definida por el usuario ( C ++ 11, 13.3.3.1.2 / 1 [over.ics.user] )

Una secuencia de conversión definida por el usuario consiste en una secuencia de conversión estándar inicial seguida de una conversión definida por el usuario seguida de una segunda secuencia de conversión estándar.

[...] Si la conversión definida por el usuario se especifica mediante una función de conversión, la secuencia de conversión estándar inicial convierte el tipo de fuente en el parámetro de objeto implícito de la función de conversión.

C ++ 11, 13.3.3.2/3 [over.ics.rank]

[...] define un orden parcial de secuencias de conversión implícitas basadas en las relaciones, una mejor secuencia de conversión y una mejor conversión.

[...] La secuencia de conversión definida por el usuario U1 es una mejor secuencia de conversión que otra secuencia de conversión definida por el usuario U2 si contienen la misma función de conversión definida por el usuario o la inicialización agregada y el constructor y la segunda secuencia de conversión estándar de U1 es mejor que la segunda secuencia de conversión estándar de U2.

La segunda conversión estándar es el caso del operator bool() es bool a bool (conversión de identidad), mientras que la segunda conversión estándar en el caso de operator int () es int a bool que es una conversión booleana.

Por lo tanto, la secuencia de conversión, utilizando el operator bool () , es mejor si ambas funciones de conversión comparten la misma calificación const.

Considere el siguiente programa corto de C ++:

#include <iostream> class B { public: operator bool() const { return false; } }; class B2 : public B { public: operator int() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; }

Si lo compilo en compiladores diferentes, obtengo varios resultados. Con Clang 3.4 y GCC 4.4.7 se imprime true , mientras que Visual Studio 2013 imprime false , lo que significa que llaman a diferentes operadores de elenco en (bool)b . ¿Cuál es el comportamiento correcto de acuerdo con el estándar?

En mi entender, el operator bool() no necesita conversión, mientras que el operator int() requeriría una conversión int a bool , por lo que el compilador debería elegir la primera. ¿ const hacer algo con eso? ¿El compilador considera que la conversión informática es más "costosa"?

Si elimino el const , todos los compiladores producen igualmente false como salida. Por otro lado, si combino las dos clases juntas (ambos operadores estarán en la misma clase), los tres compiladores producirán salida true .


Algunas de las respuestas anteriores ya proporcionan mucha información.

Mi contribución es que las "operaciones de reparto" se compilan de forma similar a las "operaciones sobrecargadas". Sugiero que se realice una función con un identificador único para cada operación y, más adelante, que se reemplace por el operador o conversión requerida.

#include <iostream> class B { public: bool ToBool() const { return false; } }; class B2 : public B { public: int ToInt() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << b.ToBool() << std::endl; }

Y, más tarde, aplique el operador o el elenco.

#include <iostream> class B { public: operator bool() { return false; } }; class B2 : public B { public: operator int() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; }

Solo mis 2 centavos.


El estándar dice:

Una función de conversión en una clase derivada no oculta una función de conversión en una clase base a menos que las dos funciones se conviertan al mismo tipo.

§12.3 [class.conv]

Lo que significa que el operator bool no está oculto por el operator int .

El estándar dice:

Durante la resolución de sobrecarga, el argumento del objeto implícito es indistinguible de otros argumentos.

§13.3.3.1 [over.match.funcs]

El "argumento de objeto implícito" en este caso es b , que es de tipo B2 & . operator bool requiere const B2 & , por lo que el compilador tendrá que agregar const a b para llamar al operator bool . Esto - en igualdad de condiciones - hace que el operator int una mejor coincidencia.

El estándar establece que un static_cast (que el elenco de estilo C está realizando en esta instancia) puede convertir a un tipo T (en este caso int ) si:

la declaración T t(e); está bien formado, para alguna variable temporal inventada t .

§5.2.9 [expr.static.cast]

Por lo tanto, el int se puede convertir a un bool , y un bool se puede convertir igualmente en un bool .

El estándar dice:

Se consideran las funciones de conversión de S y sus clases base. Las funciones de conversión no explícitas que no están ocultas en S y producen el tipo T o un tipo que se puede convertir en tipo T través de una secuencia de conversión estándar son funciones candidatas.

§13.3.1.5 [over.match.conv]

Entonces, el conjunto de sobrecarga consiste en operator int y operator bool . En igualdad de condiciones, operator int es una mejor coincidencia (ya que no es necesario agregar constness). Por lo tanto operator int debe seleccionar operator int .

Tenga en cuenta que (tal vez contra la intuición) la norma no considera el tipo de devolución (es decir, el tipo al que estos operadores convierten) una vez que se han agregado al conjunto de sobrecarga (como se estableció anteriormente), siempre que la secuencia de conversión para los argumentos de uno ellos son superiores a la secuencia de conversión para los argumentos de la otra (que, debido a la constness, es el caso en este caso).

El estándar dice:

Dadas estas definiciones, una función viable F1 se define como una función mejor que otra función viable F2 si para todos los argumentos i, ICSi (F1) no es una secuencia de conversión peor que ICSi (F2), y luego

  • para algún argumento j, ICSj (F1) es una mejor secuencia de conversión que ICSj (F2), o, si no es así,
  • el contexto es una inicialización por conversión definida por el usuario y la secuencia de conversión estándar del tipo de retorno de F1 al tipo de destino (es decir, el tipo de la entidad que se inicializa) es una secuencia de conversión mejor que la secuencia de conversión estándar del tipo de retorno de F2 al tipo de destino.

§13.3.3 [over.match.best]

En este caso, solo hay un argumento (el implícito de this parámetro). La secuencia de conversión para B2 & => B2 & (para llamar al operator int ) es superior a B2 & => const B2 & (para llamar al operator bool ), y por lo tanto el operator int se selecciona del conjunto de sobrecarga sin tener en cuenta el hecho de que en realidad no se convierte directamente en bool .


El tipo C ++ bool tiene dos valores: verdadero y falso con los valores correspondientes 1 y 0. La confusión inherente se puede evitar si se agrega un operador bool en la clase B2 que llama al operador bool de la clase base (B) explícitamente, entonces la salida viene como falso Aquí está mi programa modificado. Entonces operador bool significa operador bool y no operador int de ninguna manera.

#include <iostream> class B { public: operator bool() const { return false; } }; class B2 : public B { public: operator int() { return 5; } operator bool() { return B::operator bool(); } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; }

En su ejemplo, (bool) b estaba tratando de llamar al operador bool para B2, B2 ha heredado el operador bool, y el operador int, por la regla de dominación, se llama al operador int y al operador de bool heredado en B2. Sin embargo, al tener explícitamente un operador bool en la clase B2, el problema se resuelve.