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
Foi útil?

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:

  1. Um temporário criado por uma chamada para um construtor é um rvalue.
  2. Um RValue pode não estar vinculado a uma referência não consagrada.
  3. 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top