Considere o seguinte código:
#include <vector>
#include <iostream>
struct MySqlStream {
template<class T>
MySqlStream &operator<<( const std::vector<T> &values ) {
std::cout << "vector";
return *this;
}
};
template<class T>
MySqlStream &operator<<( MySqlStream &m, const T &x ) {
std::cout << "template";
return m;
}
int main() {
std::vector<double> temp;
MySqlStream sql;
sql << temp;
}
Tanto o g++ quanto o clang aceitam o código (e usam a vector
versão).
O MSVC, por outro lado, rejeita o código afirmando:
<source>(23): error C2593: 'operator <<' is ambiguous
<source>(6): note: could be 'MySqlStream &MySqlStream::operator <<<double>(const std::vector<double,std::allocator<double>> &)'
<source>(13): note: or 'MySqlStream &operator <<<std::vector<double,std::allocator<double>>>(MySqlStream &,const T &)'
with
[
T=std::vector<double,std::allocator<double>>
]
<source>(23): note: while trying to match the argument list '(MySqlStream, std::vector<double,std::allocator<double>>)'
O MSVC está incorreto ao rejeitar isso? Como eu poderia contornar isso? Godbolt Link
Observação: posso usar sql.operator<<( temp );
, mas isso não é muito bom quando há muitas coisas concatenadas.
g++ e clang++ estão corretos, e MSVC está incorreto: o membro
operator<<
deve ser selecionado porque é o modelo mais especializado.As principais etapas na resolução de sobrecarga são:
Crie um conjunto de funções candidatas. Para uma expressão de operador ( [over.match.oper] ), isso pode incluir:
template<class T> MySqlStream & MySqlStream::operator<<(const std::vector<T> &);
template<class T> MySqlStream & ::operator<<(MySqlStream &, const T &);
e algunsstd::operator<<
modelos de funçãoPara cada função membro não estática [template] no conjunto, trate-a como se tivesse um primeiro parâmetro extra cujo tipo é uma referência ao tipo de classe ( [over.match.funcs.general]/2 ). O argumento correspondente é baseado na expressão à esquerda de
.
or->
, que pode envolver umthis->
. Neste exemplo,template<class T> MySqlStream & MySqlStream::operator<<(MySqlStream &, const std::vector<T> &);
Para cada modelo de função no conjunto, faça a dedução do argumento do modelo para obter um tipo de função concreto ( [over.match.funcs.general]/8 , [temp.deduct] ). A dedução falha para todos os
std::operator<<
, então eles são descartados do conjunto. E obtemos:MySqlStream & operator<<(MySqlStream &, const std::vector<double> &);
MySqlStream & operator<<(MySqlStream &, const std::vector<double> &);
Verifique se cada função é "viável" ( [over.match.viable] ). Em linhas gerais, isso significa que é válido chamar a função com base em tipos de parâmetros, tipos de argumentos e restrições, mas ignorando algumas outras regras, como acesso privado ou protegido e funções excluídas. Ambas
MySqlStream
as funções são viáveis. Os candidatos internos não são viáveis e são descartados do conjunto. Neste ponto, apenas as duasMySqlStream
funções permanecem.Determine a melhor função viável ( [over.match.best] ). Aqui está a peça-chave neste exemplo.
Como os tipos de função são idênticos neste ponto, a maior parte do texto sobre Sequências de Conversão Implícitas não decide uma função melhor. A regra importante é [over.match.best.general]/(2.5) :
Novamente, um modelo de função membro não estático é considerado como tendo um primeiro parâmetro extra que é uma referência ao tipo de classe ( [temp.func.order]/3 ). Embora as regras exatas para isso não sejam idênticas à etapa anterior em [over.match.funcs.general]/2, neste caso, novamente obtemos o mesmo
template<class T> MySqlStream & MySqlStream::operator<<(MySqlStream &, const std::vector<T> &);
Nada mais em [temp.func.order] ou o relacionado [temp.deduct.partial] menciona o primeiro parâmetro especial ou o primeiro argumento para uma função membro não estática, então a única diferença importante restante são os tipos de parâmetros de função
const std::vector<T> &
vs.const T &
. Para simplificar um pouco, vamos agora olhar para um exemplo relacionado que lida com eles:A essência de [temp.deduct.partial] é inventar argumentos de modelo tão gerais quanto possível para um modelo F1, e ver se uma chamada que corresponde exatamente à especialização da função resultante também corresponde ao outro modelo original F2. Se corresponder, F1 é "pelo menos tão especializado quanto" F2, o que significa que (quase) sempre que F1 é viável, F2 também é viável. Faça isso das duas maneiras, e se apenas uma maneira for bem-sucedida, a função que era F1 naquele teste é mais especializada.
Então, primeiro vamos nos especializar
g1
: inventar um tipoT1
não relacionado a nenhuma outra declaração e substituí-lo porT
:Em seguida, veja se
g2
pode ser chamado com argumentos exatamente correspondentes. Como o parâmetro aqui é uma referência lvalue, o argumento deve ser uma expressão lvalue.Claro,
g2
deduz-se queT
éstd::vector<T1>
e é viável.g1
é pelo menos tão especializado quantog2
.Ao contrário, inventamos um tipo
struct T2 {};
e o substituímos emg2
:E pode
g1
ser chamado com argumentos exatamente correspondentes?Não, esse
const T2
argumento não é nenhuma especialização destd::vector
, então a dedução do argumento de modelo parag1
falha.g2
não é pelo menos tão especializada quantog1
. Juntos, esses significam queg1
é mais especializado queg2
.Similarmente no exemplo original, o template de função membro "vector" 1 é o template de função mais especializado e a função mais viável, e é selecionado pela resolução de sobrecarga para a
<<
expressão. A resolução de sobrecarga não é ambígua como o MSVC alega.Parece que gcc e clang estão certos. Aqui está uma citação de cppreference:
Modelo de função - cppreference.com
No seu código, o segundo argumento da função membro é
const std::vector<T> &
mais especializado do queconst T &
no caso da versão de função livre.Aqui estão alguns experimentos: