¿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];
Tenga en cuenta que
B
es una base privada. ¿Como funciona esto?Tenga en cuenta que
operator B*()
es const. ¿Por qué es importante?¿Por qué es
template<typename T> static yes check(D*, T);
mejor questatic 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.
c++
templates
overloading
implicit-conversion
typetraits
Alexey Malistov
fuente
fuente
is_base_of
: ideone.com/T0C1V Sin embargo, no funciona con versiones anteriores de GCC (GCC4.3 funciona bien).is_base_of<Base,Base>::value
debería sertrue
; esto vuelvefalse
.Respuestas:
Si estan relacionados
Supongamos por un momento que en
B
realidad es una base deD
. Luego, para la llamada acheck
, ambas versiones son viables porqueHost
se pueden convertir enD*
yB*
. Es una secuencia de conversión definida por el usuario como se describe en13.3.3.1.2
fromHost<B, D>
aD*
yB*
respectivamente. Para encontrar funciones de conversión que puedan convertir la clase, las siguientes funciones candidatas se sintetizan para la primeracheck
función de acuerdo con13.3.1.5/1
La primera función de conversión no es candidata porque
B*
no se puede convertir aD*
.Para la segunda función, existen los siguientes candidatos:
Esos son los dos candidatos a la función 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
*this
objeto no constante (el argumento del objeto implícito ) por13.3.3.2/3b1sb4
y se usa para convertir aB*
para la segundacheck
función.Si eliminara la constante, tendríamos los siguientes candidatos
Esto significaría 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, existe una puerta trasera. Si dos funciones de conversión son igualmente buenas, entonces el tipo de retorno de ellas decide cuál es la mejor según
13.3.3/1
. Por lo tanto, si eliminara la constante, se tomaría la primera, porqueB*
convierte mejor aB*
queD*
aB*
.Ahora bien, ¿qué secuencia de conversión definida por el usuario es mejor? ¿El de la segunda o primera función de verificación? La regla es que las secuencias de conversión definidas por el usuario solo se pueden comparar si utilizan 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. Observe que, por tanto, la constante 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 retorno 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 aD*
que aB*
. ¡Así se selecciona la primera función y reconocemos la herencia!Tenga en cuenta que dado que nunca necesitamos realmente convertir a una clase base, podemos reconocer con ello la herencia privada , porque si podemos convertir de una
D*
a unaB*
no depende de la forma de herencia de acuerdo con la4.10/3
Si no están relacionados
Ahora supongamos que no están relacionados por herencia. Así, para la primera función tenemos los siguientes candidatos
Y por el segundo ahora tenemos otro conjunto
Como no podemos convertir
D*
aB*
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 tanto, seríamos ambiguos si no fuera por el hecho de que la primera función es una plantilla. Las plantillas son la segunda opción cuando hay una función que no es de plantilla que es igualmente buena de acuerdo con13.3.3/1
. Por lo tanto, seleccionamos la función sin plantilla (la segunda) y reconocemos que no hay herencia entreB
yD
!fuente
std::is_base_of<...>
. Todo está bajo el capó.boost::
necesitan asegurarse de tener estos elementos intrínsecos disponibles antes de usarlos. Y tengo la sensación de que hay una especie de mentalidad de "desafío aceptado" entre ellos para implementar cosas sin la ayuda del compilador :)Averigüemos cómo funciona mirando los pasos.
Empiece por la
sizeof(check(Host<B,D>(), int()))
pieza. El compilador puede ver rápidamente que secheck(...)
trata de una expresión de llamada a función, por lo que debe realizar una resolución de sobrecargacheck
. Hay dos posibles sobrecargas disponiblestemplate <typename T> yes check(D*, T);
yno check(B*, int);
. Si se elige el primero, obtienessizeof(yes)
, elsesizeof(no)
A continuación, veamos la resolución de sobrecarga. La primera sobrecarga es una instanciación de plantilla
check<int> (D*, T=int)
y el segundo candidato escheck(B*, int)
. Los argumentos reales proporcionados sonHost<B,D>
yint()
. 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 mire 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. Para el segundo, la sobrecarga es más complicada. Necesitamos una B *, pero posiblemente haya dos secuencias de conversión. Uno es viaHost<B,D>::operator B*() const
. Si (y sólo si) B y D están relacionados por herencia será la secuencia de conversiónHost<B,D>::operator D*()
+D*->B*
existir. Ahora suponga que D de hecho hereda de B. Las dos secuencias de conversión sonHost<B,D> -> Host<B,D> const -> operator B* const -> B*
yHost<B,D> -> operator D* -> D* -> B*
.Entonces, para B y D relacionados,
no check(<Host<B,D>(), int())
sería ambiguo. Como resultado,yes check<int>(D*, int)
se elige la plantilla . Sin embargo, si D no hereda de B, entoncesno check(<Host<B,D>(), int())
no es ambiguo. En este punto, la resolución de la sobrecarga no puede ocurrir según la secuencia de conversión más corta. Sin embargo, dadas secuencias de conversión iguales, la resolución de sobrecarga prefiere funciones sin plantilla, es decirno check(B*, int)
.Ahora ve por qué no importa que la herencia sea privada: esa relación solo sirve para eliminar la
no check(Host<B,D>(), int())
resolución de sobrecarga antes de que ocurra la verificación de acceso. Y también ve por quéoperator B* const
debe ser constante: de lo contrario, no es necesario elHost<B,D> -> Host<B,D> const
paso, no hay ambigüedad yno check(B*, int)
siempre se elegiría.fuente
const
. Si su respuesta es verdadera, entoncesconst
se necesita no. Pero no es cierto. Quitarconst
y truco no funcionará.no check(B*, int)
ya no son ambiguas.no check(B*, int)
, entonces por relacionadosB
yD
, no sería ambiguo. El compilador elegiría sin ambigüedadesoperator D*()
realizar la conversión porque no tiene una constante. Es un poco en la dirección opuesta: si elimina la const, introduce una sensación de ambigüedad, pero que se resuelve por el hecho de queoperator B*()
proporciona un tipo de retorno superior que no necesita una conversión de puntero aB*
likeD*
does.B*
del<Host<B,D>()
temporal.El
private
bit es completamente ignorado poris_base_of
porque la resolución de sobrecarga ocurre antes de las comprobaciones de accesibilidad.Puede verificar esto simplemente:
Lo mismo se aplica aquí, el hecho de que
B
sea una base privada no impide que se lleve a cabo la verificación, solo evitaría la conversión, pero nunca pedimos la conversión real;)fuente
host
se convierte arbitrariamente enD*
oB*
en la expresión no evaluada. Por alguna razón,D*
es preferible enB*
determinadas condiciones.Posiblemente tenga algo que ver con el ordenamiento parcial de la resolución de sobrecarga. D * es más especializado que B * en el caso de que D se derive de B.
Los detalles exactos son bastante complicados. Debe averiguar las precedentes de varias reglas de resolución de sobrecarga. El pedido parcial es uno. Longitudes / tipos de secuencias de conversión es otro. Finalmente, si dos funciones viables se consideran igualmente buenas, se eligen no plantillas en lugar de plantillas de funciones.
Nunca necesité buscar cómo interactúan estas reglas. Pero parece que el ordenamiento parcial está dominando las otras reglas de resolución de sobrecarga. Cuando D no se deriva de B, las reglas de ordenación parcial no se aplican y la no plantilla es más atractiva. Cuando D se deriva de B, el orden parcial se activa y hace que la plantilla de función sea más atractiva, como parece.
En cuanto a que la herencia sea privada: el código nunca solicita una conversión de D * a B *, lo que requeriría una herencia pública.
fuente
is_base_of
y los bucles por los que pasaron los contribuyentes para garantizar esto.The exact details are rather complicated
- ese es el punto. Por favor explique. Yo quiero saberSiguiendo con su segunda pregunta, tenga en cuenta que si no fuera por const, Host estaría mal formado si se instancia con B == D. Pero is_base_of está diseñado de tal manera que cada clase es una base de sí misma, por lo tanto, uno de los operadores de conversión debe ser const.
fuente