Como funciona o código a seguir?
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];
Observe que
B
é uma base privada. Como é que isso funciona?Observe que
operator B*()
é const. Por que isso é importante?Porque é
template<typename T> static yes check(D*, T);
melhor do questatic yes check(B*, int);
?
Nota : É uma versão reduzida (macros são removidas) de boost::is_base_of
. E isso funciona em uma ampla gama de compiladores.
c++
templates
overloading
implicit-conversion
typetraits
Alexey Malistov
fonte
fonte
is_base_of
: ideone.com/T0C1V Ele não funciona com versões mais antigas do GCC (GCC4.3 funciona bem).is_base_of<Base,Base>::value
deve sertrue
; isso retornafalse
.Respostas:
Se eles são relacionados
Por um momento, vamos supor que
B
seja realmente uma base deD
. Então, para a chamada paracheck
, ambas as versões são viáveis porqueHost
podem ser convertidas paraD*
eB*
. É uma sequência de conversão definida pelo usuário, conforme descrito por13.3.3.1.2
deHost<B, D>
paraD*
eB*
respectivamente. Para encontrar funções de conversão que podem converter a classe, as seguintes funções candidatas são sintetizadas para a primeiracheck
função de acordo com13.3.1.5/1
A primeira função de conversão não é candidata, porque
B*
não pode ser convertida paraD*
.Para a segunda função, existem os seguintes candidatos:
Esses são os dois candidatos à função de conversão que usam o objeto host. O primeiro o considera por referência const e o segundo não. Assim, o segundo é uma combinação melhor para o
*this
objeto não const (o argumento do objeto implícito ) por13.3.3.2/3b1sb4
e é usado para converter paraB*
para a segundacheck
função.Se você remover o const, teríamos os seguintes candidatos
Isso significaria que não podemos mais selecionar por constância. Em um cenário de resolução de sobrecarga comum, a chamada agora seria ambígua porque normalmente o tipo de retorno não participará da resolução de sobrecarga. Para funções de conversão, no entanto, existe uma porta dos fundos. Se duas funções de conversão forem igualmente boas, o tipo de retorno delas decidirá quem é a melhor de acordo com
13.3.3/1
. Portanto, se você remover o const, o primeiro será usado, porqueB*
converte melhor em doB*
queD*
emB*
.Agora, qual sequência de conversão definida pelo usuário é melhor? Aquele para a segunda ou a primeira função de verificação? A regra é que as sequências de conversão definidas pelo usuário só podem ser comparadas se usarem a mesma função de conversão ou construtor de acordo com
13.3.3.2/3b2
. Este é exatamente o caso aqui: Ambos usam a segunda função de conversão. Observe que, portanto, const é importante porque força o compilador a assumir a segunda função de conversão.Já que podemos compará-los - qual é o melhor? A regra é que a melhor conversão do tipo de retorno da função de conversão para o tipo de destino vence (novamente por
13.3.3.2/3b2
). Nesse caso,D*
converte melhor em doD*
que emB*
. Assim, a primeira função é selecionada e reconhecemos a herança!Observe que, uma vez que nunca precisamos realmente converter para uma classe base, podemos reconhecer a herança privada porque se podemos converter de um
D*
para aB*
não depende da forma de herança de acordo com4.10/3
Se eles não são relacionados
Agora vamos assumir que eles não estão relacionados por herança. Assim, para a primeira função, temos os seguintes candidatos
E para o segundo, agora temos outro conjunto
Visto que não podemos converter
D*
paraB*
se não tivermos uma relação de herança, agora não temos nenhuma função de conversão comum entre as duas sequências de conversão definidas pelo usuário! Assim, seríamos ambíguos se não fosse o fato de que a primeira função é um modelo. Os modelos são a segunda escolha quando há uma função não-modelo que é igualmente boa de acordo com13.3.3/1
. Assim, selecionamos a função não modelo (a segunda) e reconhecemos que não há herança entreB
eD
!fonte
std::is_base_of<...>
. Está tudo sob o capô.boost::
precisam ter certeza de que têm esses intrínsecos disponíveis antes de usá-los. E eu tenho a sensação de que há algum tipo de mentalidade de "desafio aceito" entre eles para implementar coisas sem a ajuda do compilador :)Vamos descobrir como isso funciona observando as etapas.
Comece com a
sizeof(check(Host<B,D>(), int()))
parte. O compilador pode ver rapidamente que estacheck(...)
é uma expressão de chamada de função, portanto, ele precisa fazer a resolução de sobrecargacheck
. Existem duas sobrecargas candidatas disponíveis,template <typename T> yes check(D*, T);
eno check(B*, int);
. Se o primeiro for escolhido, você obtémsizeof(yes)
, senãosizeof(no)
A seguir, vamos examinar a resolução de sobrecarga. A primeira sobrecarga é uma instanciação de modelo
check<int> (D*, T=int)
e a segunda candidata écheck(B*, int)
. Os argumentos reais fornecidos sãoHost<B,D>
eint()
. O segundo parâmetro claramente não os distingue; apenas serviu para tornar a primeira sobrecarga um modelo. Veremos mais tarde porque a parte do modelo é relevante.Agora veja as sequências de conversão necessárias. Para a primeira sobrecarga, temos
Host<B,D>::operator D*
- uma conversão definida pelo usuário. Para o segundo, a sobrecarga é mais complicada. Precisamos de um B *, mas possivelmente existem duas sequências de conversão. Um é viaHost<B,D>::operator B*() const
. Se (e somente se) B e D estão relacionados por herança será a sequência de conversãoHost<B,D>::operator D*()
+D*->B*
existir. Agora suponha que D realmente herda de B. As duas sequências de conversão sãoHost<B,D> -> Host<B,D> const -> operator B* const -> B*
eHost<B,D> -> operator D* -> D* -> B*
.Portanto, para B e D relacionados,
no check(<Host<B,D>(), int())
seria ambíguo. Como resultado, o modeloyes check<int>(D*, int)
é escolhido. No entanto, se D não herda de B, entãono check(<Host<B,D>(), int())
não é ambíguo. Neste ponto, a resolução de sobrecarga não pode acontecer com base na seqüência de conversão mais curta. No entanto, sequências de igual conversão dadas, a resolução de sobrecarga prefere funções não-molde, ou sejano check(B*, int)
.Você agora vê por que não importa que a herança seja privada: essa relação serve apenas para eliminar a
no check(Host<B,D>(), int())
resolução de sobrecarga antes que a verificação de acesso aconteça. E você também vê por queoperator B* const
deve ser constante: do contrário, não há necessidade daHost<B,D> -> Host<B,D> const
etapa, nenhuma ambigüidade eno check(B*, int)
sempre seria escolhido.fonte
const
. Se sua resposta for verdadeira, nãoconst
é necessário. Mas não é verdade. Removaconst
e o truque não funcionará.no check(B*, int)
não são mais ambíguas.no check(B*, int)
, então para relacionadosB
eD
, não seria ambíguo. O compilador sem ambigüidade escolheriaoperator D*()
realizar a conversão porque não tem um const. É um pouco na direção oposta: se você remover const, introduzirá algum senso de ambigüidade, mas que é resolvido pelo fato de queoperator B*()
fornece um tipo de retorno superior que não precisa de uma conversão de ponteiro paraB*
likeD*
.B*
do<Host<B,D>()
temporário.O
private
bit é completamente ignorado poris_base_of
porque a resolução da sobrecarga ocorre antes das verificações de acessibilidade.Você pode verificar isso simplesmente:
O mesmo se aplica aqui, o facto de
B
ser uma base privada não impede a realização da verificação, apenas impediria a conversão, mas nunca pedimos a conversão efectiva;)fonte
host
é convertido arbitrariamente paraD*
ouB*
na expressão não avaliada. Por alguma razão,D*
é preferívelB*
sob certas condições.Possivelmente tem algo a ver com o pedido parcial em relação à resolução de sobrecarga. D * é mais especializado do que B * no caso de D derivar de B.
Os detalhes exatos são bastante complicados. Você tem que descobrir as precedências de várias regras de resolução de sobrecarga. A ordenação parcial é uma. Comprimentos / tipos de sequências de conversão são outro. Finalmente, se duas funções viáveis forem consideradas igualmente boas, os não modelos são escolhidos em vez dos modelos de função.
Nunca precisei pesquisar como essas regras interagem. Mas parece que o pedido parcial está dominando as outras regras de resolução de sobrecarga. Quando D não deriva de B, as regras de ordenação parcial não se aplicam e o não modelo é mais atraente. Quando D deriva de B, a ordenação parcial entra em ação e torna o modelo de função mais atraente - como parece.
Quanto ao fato de a herança ser privada: o código nunca pede uma conversão de D * para B * que exigiria herança pública.
fonte
is_base_of
e os loops pelos quais os contribuidores passaram para garantir isso.The exact details are rather complicated
- essa é a questão. Por favor explique. Eu quero saber.Seguindo sua segunda pergunta, observe que se não fosse por const, o Host seria mal formado se instanciado com B == D. Mas is_base_of é projetado de forma que cada classe seja uma base de si mesma, portanto, um dos operadores de conversão deve seja const.
fonte