c++ templates overloading implicit-conversion typetraits

c++ - ¿Cómo funciona `is_base_of`?



templates overloading (5)

Si están relacionados

Supongamos por un momento que B es realmente una base de D Luego, para que la llamada se check , ambas versiones son viables porque Host se puede convertir a D* y B* . Es una secuencia de conversión definida por el usuario como se describe en 13.3.3.1.2 del Host<B, D> a D* y B* respectivamente. Para encontrar funciones de conversión que puedan convertir la clase, se sintetizan las siguientes funciones candidatas para la primera función de check acuerdo con 13.3.1.5/1

D* (Host<B, D>&)

La primera función de conversión no es candidata, porque B* no se puede convertir a D* .

Para la segunda función, existen los siguientes candidatos:

B* (Host<B, D> const&) D* (Host<B, D>&)

Esos son los dos candidatos a funciones de conversión que toman el objeto host. El primero lo toma por referencia constante, y el segundo no. Por lo tanto, el segundo es una mejor coincidencia para el objeto no const *this objeto (el argumento del objeto implícito ) por 13.3.3.2/3b1sb4 y se usa para convertir a B* para la segunda función de check .

Si eliminas la const, tendremos los siguientes candidatos

B* (Host<B, D>&) D* (Host<B, D>&)

Esto significa que ya no podemos seleccionar por constness. En un escenario de resolución de sobrecarga normal, la llamada ahora sería ambigua porque normalmente el tipo de retorno no participará en la resolución de sobrecarga. Para las funciones de conversión, sin embargo, hay una puerta trasera. Si dos funciones de conversión son igualmente buenas, entonces el tipo de retorno de ellas decide quién es el mejor según 13.3.3/1 . Por lo tanto, si quita la const, entonces la primera se tomaría, porque B* convierte mejor a B* que D* a B* .

Ahora, ¿qué secuencia de conversión definida por el usuario es mejor? ¿El de la segunda o la primera función de verificación? La regla es que las secuencias de conversión definidas por el usuario solo pueden compararse si usan la misma función de conversión o constructor de acuerdo con 13.3.3.2/3b2 . Este es exactamente el caso aquí: ambos usan la segunda función de conversión. Tenga en cuenta que, por lo tanto, la const es importante porque obliga al compilador a tomar la segunda función de conversión.

Ya que podemos compararlos, ¿cuál es mejor? La regla es que la mejor conversión del tipo de devolución de la función de conversión al tipo de destino gana (nuevamente por 13.3.3.2/3b2 ). En este caso, D* convierte mejor a D* que a B* . ¡Así se selecciona la primera función y reconocemos la herencia!

Tenga en cuenta que dado que nunca necesitamos convertir realmente a una clase base, podemos reconocer la herencia privada porque si podemos convertir de un D* a un B* no depende de la forma de herencia de acuerdo con 4.10/3

Si no están relacionados

Ahora supongamos que no están relacionados por herencia. Por lo tanto, para la primera función tenemos los siguientes candidatos

D* (Host<B, D>&)

Y por el momento, ahora tenemos otro juego

B* (Host<B, D> const&)

Como no podemos convertir D* a B* si no tenemos una relación de herencia, ahora no tenemos una función de conversión común entre las dos secuencias de conversión definidas por el usuario. Por lo tanto, seríamos ambiguos si no fuera por el hecho de que la primera función es una plantilla. Las plantillas son de segunda elección cuando hay una función que no es de plantilla que es igualmente buena de acuerdo con 13.3.3/1 . Por lo tanto, seleccionamos la función sin plantilla (la segunda) y reconocemos que no hay herencia entre B y D

¿Cómo funciona el siguiente código?

typedef char (&yes)[1]; typedef char (&no)[2]; template <typename B, typename D> struct Host { operator B*() const; operator D*(); }; template <typename B, typename D> struct is_base_of { template <typename T> static yes check(D*, T); static no check(B*, int); static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes); }; //Test sample class Base {}; class Derived : private Base {}; //Expression is true. int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];

  1. Tenga en cuenta que B es una base privada. ¿Como funciona esto?

  2. Tenga en cuenta que el operator B*() es const. ¿Por qué es importante?

  3. ¿Por qué la template<typename T> static yes check(D*, T); mejor que static yes check(B*, int); ?

Nota : Es una versión reducida (se eliminan las macros) de boost::is_base_of . Y esto funciona en una amplia gama de compiladores.


A continuación de su segunda pregunta, tenga en cuenta que si no fuera por const, Host estaría mal formado si se instanciara con B == D. Pero is_base_of está diseñado de tal manera que cada clase es una base de sí mismo, por lo tanto uno de los operadores de conversión debe se const.


Posiblemente tiene algo que ver con la resolución de sobrecarga de wrt de ordenamiento parcial. D * es más especializada que B * en caso de que D se derive de B.

Los detalles exactos son bastante complicados. Debe averiguar las precedencias de varias reglas de resolución de sobrecarga. El orden parcial es uno. Longitudes / clases de secuencias de conversión es otra. Finalmente, si dos funciones viables se consideran igualmente buenas, se eligen no plantillas sobre plantillas de funciones.

Nunca necesité ver cómo interactúan estas reglas. Pero parece que el orden parcial está dominando las otras reglas de resolución de sobrecarga. Cuando D no deriva de B, las reglas de ordenamiento parcial no se aplican y la no plantilla es más atractiva. Cuando D se deriva de B, comienza el orden parcial y hace que la plantilla de la función sea más atractiva, como parece.

En cuanto a la herencia privada: el código nunca solicita una conversión de D * a B * que requeriría herencia pública.


Vamos a ver cómo funciona mirando los pasos.

Comience con la parte sizeof(check(Host<B,D>(), int())) . El compilador puede ver rápidamente que esta check(...) es una expresión de llamada de función, por lo que necesita hacer una resolución de sobrecarga al check . Hay dos sobrecargas candidatas disponibles, template <typename T> yes check(D*, T); y no check(B*, int); . Si se elige el primero, se obtiene sizeof(yes) , else sizeof(no)

A continuación, veamos la resolución de sobrecarga. La primera sobrecarga es una check<int> (D*, T=int) instanciación de plantilla check<int> (D*, T=int) y el segundo candidato es check(B*, int) . Los argumentos reales proporcionados son Host<B,D> e int() . El segundo parámetro claramente no los distingue; simplemente sirvió para convertir la primera sobrecarga en una plantilla. Veremos más adelante por qué la parte de la plantilla es relevante.

Ahora mira las secuencias de conversión que se necesitan. Para la primera sobrecarga, tenemos Host<B,D>::operator D* - una conversión definida por el usuario. Por el segundo, la sobrecarga es más complicada. Necesitamos una B *, pero posiblemente hay dos secuencias de conversión. Uno es a través de Host<B,D>::operator B*() const . Si (y solo si) B y D están relacionados por herencia, existirá la secuencia de conversión Host<B,D>::operator D*() + D*->B* . Ahora supongamos que D hereda de B. Las dos secuencias de conversión son Host<B,D> -> Host<B,D> const -> operator B* const -> B* y Host<B,D> -> operator D* -> D* -> B* .

Entonces, para B y D relacionados, no check(<Host<B,D>(), int()) sería ambigua. Como resultado, se elige la opción yes check<int>(D*, int) . Sin embargo, si D no hereda de B, entonces no check(<Host<B,D>(), int()) no es ambigua. En este punto, la resolución de sobrecarga no puede ocurrir en la secuencia de conversión más corta. Sin embargo, dadas las mismas secuencias de conversión, la resolución de sobrecarga prefiere las funciones que no son de plantilla, es decir, no check(B*, int) .

Ahora verá por qué no importa que la herencia sea privada: esa relación solo sirve para eliminar la falta de no check(Host<B,D>(), int()) de la resolución de sobrecarga antes de que se realice la verificación de acceso. Y también se ve por qué el operator B* const debe ser const: de lo contrario no es necesario el Host<B,D> -> Host<B,D> const paso, sin ambigüedad, y no check(B*, int) haría siempre ser elegido.


is_base_of ignora por completo el bit private porque se produce una resolución de sobrecarga antes de las comprobaciones de accesibilidad.

Puedes verificar esto simplemente:

class Foo { public: void bar(int); private: void bar(double); }; int main(int argc, char* argv[]) { Foo foo; double d = 0.3; foo.bar(d); // Compiler error, cannot access private member function }

Lo mismo aplica aquí, el hecho de que B es una base privada no impide que el cheque se lleve a cabo, solo evitaría la conversión, pero nunca pedimos la conversión real;)