Por que o compilador Visual C ++ está chamando a sobrecarga errada aqui?
-
03-07-2019 - |
Pergunta
Por que o compilador Visual C ++ está chamando a sobrecarga errada aqui?
Tenho uma subclasse de Ostream que uso para definir um buffer para formatação. Às vezes, quero criar um temporário e inserir imediatamente uma string nela com o usual << Operador como este:
M2Stream() << "the string";
Infelizmente, o programa chama a sobrecarga do operador << (Ostream, void *), em vez do operador << (Ostream, const char *) não -membro.
Escrevi a amostra abaixo como um teste em que defino minha própria classe M2stream que reproduz o problema.
Eu acho que o problema é que a expressão M2Stream () produz uma temporária e isso de alguma forma faz com que o compilador prefira a sobrecarga do vazio *. Mas por que? Isso é confirmado pelo fato de que, se eu fizer o primeiro argumento para a sobrecarga não -membro, const M2Stream &, eu recebo uma ambiguidade.
Outra coisa estranha é que ele chama a sobrecarga const Char * desejada se eu definir primeiro uma variável do tipo const char * e depois chamá -lo, em vez de uma string literal de char, assim:
const char *s = "char string variable";
M2Stream() << s;
É como se a sequência literal tivesse um tipo diferente da variável const char *! Eles não deveriam ser iguais? E por que o compilador causa uma chamada para a sobrecarga do vazio * quando uso o string temporário e o char literal?
#include "stdafx.h"
#include <iostream>
using namespace std;
class M2Stream
{
public:
M2Stream &operator<<(void *vp)
{
cout << "M2Stream bad operator<<(void *) called with " << (const char *) vp << endl;
return *this;
}
};
/* If I make first arg const M2Stream &os, I get
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(39) : error C2666: 'M2Stream::operator <<' : 2 overloads have similar conversions
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(13): could be 'M2Stream &M2Stream::operator <<(void *)'
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(20): or 'const M2Stream &operator <<(const M2Stream &,const char *)'
while trying to match the argument list '(M2Stream, const char [45])'
note: qualification adjustment (const/volatile) may be causing the ambiguity
*/
const M2Stream & operator<<(M2Stream &os, const char *val)
{
cout << "M2Stream good operator<<(const char *) called with " << val << endl;
return os;
}
int main(int argc, char argv[])
{
// This line calls void * overload, outputs: M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream() << "literal char string on constructed temporary";
const char *s = "char string variable";
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
M2Stream() << s;
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
M2Stream m;
m << "literal char string on prebuilt object";
return 0;
}
Resultado:
M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream good operator<<(const char *) called with char string variable
M2Stream good operator<<(const char *) called with literal char string on prebuilt object
Solução
O compilador está fazendo a coisa certa: Stream() << "hello";
deve usar o operator<<
definido como uma função de membro. Como o objeto de fluxo temporário não pode estar vinculado a uma referência não-consistente, mas apenas a uma referência const, o operador não-membro que lida char const*
não será selecionado.
E foi projetado dessa maneira, como você vê quando você altera esse operador. Você obtém ambiguidades, porque o compilador não pode decidir qual dos operadores disponíveis usar. Porque todos eles foram projetados com a rejeição do não-membro operator<<
em mente para temporários.
Então, sim, uma corda literal tem um tipo diferente de um char const*
. Uma string literal é uma variedade de caracteres const. Mas isso não importaria no seu caso, eu acho. Eu não sei o que sobrecarga operator<<
MSVC ++ adiciona. É permitido adicionar mais sobrecargas, desde que não afetem o comportamento de programas válidos.
Pois M2Stream() << s;
Funciona mesmo quando o primeiro parâmetro é uma referência não-consultiva ... Bem, o MSVC ++ tem uma extensão que permite que referências não consagras se ligam aos temporários. Coloque o nível de alerta no nível 4 para ver um aviso sobre isso (algo como "extensão fora do padrão usado ...").
Agora, porque existe um operador membro << que leva um void const*
, e a char const*
pode se converter para isso, esse operador será escolhido e o endereço será produzido como é isso que o void const*
sobrecarga é para.
Eu já vi em seu código que você realmente tem um void*
sobrecarga, não um void const*
sobrecarga. Bem, um literal de corda pode se converter para char*
, mesmo que o tipo de string literal seja char const[N]
(com N sendo a quantidade de caracteres que você coloca). Mas essa conversão é preterida. Não deve ser padrão que um literal de string se converte para void*
. Parece -me que é outra extensão do compilador MSVC ++. Mas isso explicaria por que o literal da corda é tratado de maneira diferente do char const*
ponteiro. É isso que o padrão diz:
Uma corda literal (2.13.4) que não é um amplo literal pode ser convertida em um rvalue do tipo "ponteiro para char"; Uma corda ampla literal pode ser convertida em um Rvalue do tipo "ponteiro para wchar_t". Em ambos os casos, o resultado é um ponteiro para o primeiro elemento da matriz. Essa conversão é considerada apenas quando existe um tipo de alvo de ponteiro apropriado explícito, e não quando há uma necessidade geral de converter de um LValue em um Rvalue. [Nota: esta conversão está obsoleta. Veja o Anexo D.
Outras dicas
O primeiro problema é causado por regras de idioma C ++ estranhas e complicadas:
- Um temporário criado por uma chamada para um construtor é um rvalue.
- Um RValue pode não estar vinculado a uma referência não consagrada.
- No entanto, um objeto RValue pode ter métodos que não sejam consagrados.
O que está acontecendo é que ostream& operator<<(ostream&, const char*)
, uma função não membro, tenta vincular o M2Stream
Temporário você cria para uma referência não consaginada, mas isso falha (regra nº 2); mas ostream& ostream::operator<<(void*)
é uma função de membro e, portanto, pode se ligar a ela. Na ausência do const char*
função, é selecionado como a melhor sobrecarga.
Não sei por que os designers da biblioteca iostreams decidiram fazer operator<<()
por void*
um método, mas não operator<<()
por const char*
, mas é assim que é, então temos essas estranhas inconsistências para lidar.
Não sei por que o segundo problema está ocorrendo. Você obtém o mesmo comportamento em diferentes compiladores? É possível que seja um compilador ou biblioteca padrão C ++, mas eu deixaria isso como desculpa de último recurso - pelo menos veja se você pode replicar o comportamento com um regular ostream
primeiro.
O problema é que você está usando um objeto de fluxo temporário. Altere o código para o seguinte e funcionará:
M2Stream ms;
ms << "the string";
Basicamente, o compilador está se recusando a ligar o temporário à referência não const.
Em relação ao seu segundo ponto sobre por que ele se liga quando você tem um objeto "const char *", acredito que é um bug no compilador VC. Não posso dizer com certeza, no entanto, quando você tem apenas a corda literal, há uma conversão para 'void *' e uma conversão para 'const char *'. Quando você possui o objeto 'const char *', não há conversão necessária no segundo argumento - e isso pode ser um gatilho para o comportamento não padrão do VC para permitir a ligação não const.
Eu acredito que 8.5.3/5 é a seção do padrão que cobre isso.
Não tenho certeza se seu código deve compilar. Eu penso:
M2Stream & operator<<( void *vp )
deveria estar:
M2Stream & operator<<( const void *vp )
De fato, olhando mais para o código, acredito que todos os seus problemas estão constentes. O código a seguir funciona conforme o esperado:
#include <iostream>
using namespace std;
class M2Stream
{
};
const M2Stream & operator<<( const M2Stream &os, const char *val)
{
cout << "M2Stream good operator<<(const char *) called with " << val << endl;
return os;
}
int main(int argc, char argv[])
{
M2Stream() << "literal char string on constructed temporary";
const char *s = "char string variable";
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
M2Stream() << s;
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
M2Stream m;
m << "literal char string on prebuilt object";
return 0;
}
Você pode usar uma sobrecarga como esta:
template <int N>
M2Stream & operator<<(M2Stream & m, char const (& param)[N])
{
// output param
return m;
}
Como um bônus adicional, agora você sabe que não é o comprimento da matriz.