Tipo de ponteiro para membro da classe base
-
27-09-2019 - |
Pergunta
Eu tenho um problema com os indicadores de membros. O código a seguir falha em compilar usando o CC do Oracle Solaris Studio 12.2 e o Cygwin GCC 4.3.4, mas funciona com o Microsoft Visual C ++ 2010:
struct A {
int x;
};
struct B : public A {
};
template<typename T> class Bar {
public:
template<typename M> void foo(M T::*p);
};
int main(int, char *[]) {
Bar<B> bbar;
bbar.foo(&B::x);
return 0;
}
Na linha próxima da última linha, ambos os compiladores mencionados acima não conseguem encontrar uma correspondência para Bar<B>::foo(int A::*)
. Eu escrevi um teste simples para confirmar que o tipo de expressão &B::x
é na verdade int A::*
:
// ...
static void foo(int A::*p) {
std::cout << "A" << std::endl;
}
static void foo(int B::*p) {
std::cout << "B" << std::endl;
}
int main(int, char *[]) {
foo(&B::x); // prints "A", even on MS VC++ 2010
return 0;
}
A solução alternativa a seguir funciona com o GCC (ainda não testado com o Oracle CC), mas falha com o VC ++ devido à ambiguidade:
template<typename T> class Bar {
public:
template<typename M> void foo(M T::*p);
template<typename M, typename _T_base> inline void foo(M _T_base::*p) {
foo(static_cast<M T::*>(p));
}
};
Minha pergunta: qual comportamento está correto? Aparentemente, o VC ++ faz um upcast implícito de int A::*
para int B::*
Para satisfazer a chamada para o modelo de função do membro, os outros dois compiladores não devem considerar fazer o mesmo?
Solução
Uma conversão de int A::*
para int B::*
é permitido, e esse não é o problema. O problema está na dedução do argumento do modelo, como você pode ver se você tenta o seguinte programa que fornece um argumento de modelo <int>
por B::foo
e compilar e uma função não-membro foo2
que produz o mesmo erro que B::foo
antes.
struct A {
int x;
};
struct B : public A {
};
template <typename T> class Bar {
public:
template<typename M> void foo(M T::*p);
};
template<typename M> void foo2(M B::*p);
int main(int, char*[]) {
Bar<B> bbar;
bbar.foo<int>(&B::x);
foo2(&B::x); // error, but foo2<int>(&B::x) would work.
return 0;
}
Eu acho que esta situação não está coberta pelos casos em que o compilador deve deduzir o argumento do modelo <int>
sozinho. 14.8.2.1p3:
Em geral, o processo de dedução tenta encontrar valores de argumento do modelo que tornarão o deduzido um idêntico a (após o tipo A ser transformado como descrito acima). No entanto, existem três casos que permitem uma diferença:
- Se o P original for um tipo de referência, o deduzido a (isto é, o tipo referido pela referência) pode ser mais qualificado pelo CV do que A.
- A pode ser outro ponteiro ou ponteiro para o tipo de membro que pode ser convertido para deduzido A através de uma conversão de qualificação (conv.qual).
- Se P é uma classe, e P tem o modelo de formulário-Id, então A pode ser uma classe derivada do deduzido A. Da mesma forma, se p é um ponteiro para uma classe do modelo de formulário, um ponteiro pode ser um ponteiro para uma classe derivada apontada pelos deduzidos A.
Aqui "P" é o tipo de argumento da função de modelo: M B::*p
, onde o parâmetro de tipo de modelo M
deve ser determinado. "A" é o tipo de argumento real: int A::*
. P e A certamente não são uma referência ou uma classe, e o tipo de conversão de ponteiro para membro que precisaríamos que isso funcione não é uma conversão de qualificação (que descreve apenas manipulações constantes/voláteis como X*
para const X*
ou int X::*
para const int X::*
).
Portanto, o argumento do modelo não pode ser deduzido e você deve adicionar o <int>
Parâmetro de modelo explícito para o seu código.