Domanda

Come esercizio di apprendimento, ho osservato come funziona la conversione automatica dei tipi in C ++. so che la conversione automatica dei tipi dovrebbe essere generalmente evitata, ma vorrei aumentare la mia conoscenza del C ++ capendo come funziona comunque.

Ho creato una classe StdStringConverter che può essere automaticamente convertita in std::string, ma il compilatore (g ++ 4.3.4 su Debian) sembra non fare la conversione quando l'oggetto viene confrontato con un reale CStringConverter (ignora la mancanza di passaggi per riferimento e la creazione non necessaria di oggetti temporanei):

#include <string>

class StdStringConverter
{
public:
    explicit StdStringConverter(std::string name) : m_name(name) {}
    operator const std::string () const { return m_name; }
private:
    std::string m_name;
};

int main()
{
    StdStringConverter converter(std::string("Me"));
    const std::string name = "Me";
    // Next line causes compiler error:
    // no match for 'operator==' in 'converter == name'
    return (converter == name) ? 0 : 1;
}

D'altra parte, se lo cambio leggermente in una char classe, avviene la conversione automatica , sebbene il confronto di char* puntatori probabilmente non è quello che intendevo:

#include <string>

class CStringConverter
{
public:
    explicit CStringConverter(std::string name) : m_name(name) {}
    operator const char* () const { return m_name.c_str(); }
private:
    std::string m_name;
};

int main()
{
    CStringConverter converter(std::string("Me"));
    const char* name = "Me";
    // Next line compiles fine, but they are not equal because the
    // pointers don't match.
    return (converter == name) ? 0 : 1;
}

C'è qualcosa di speciale nella differenza tra un <=> e un <=> in questo contesto che impedisce al compilatore di trattarli allo stesso modo?

È stato utile?

Soluzione

Il problema è dovuto al fatto che std :: string è in realtà un'istanza del modello di classe std :: basic_string. Un operatore == disponibile nello spazio dei nomi std accetta due modelli std :: basic_string:


template<class charT, class traits, class Allocator>
bool operator==(const basic_string& lhs,
                const basic_string& rhs);

Se questa versione dell'operatore == fosse sovraccaricata specificatamente su std :: string, il tuo codice andrebbe bene. Ma non è così, il che richiederebbe al compilatore di eseguire la deduzione dell'argomento template sui parametri template di std :: basic_string in modo da capire che il ritorno del tuo operatore di conversione è una possibile corrispondenza.

Tuttavia, il compilatore non lo farà. Non so quale parte dello standard affermi esattamente questo. Ma l'idea generale è che tali conversioni funzionano solo per tipi non template.

Una cosa che posso suggerire è di inserire StdStringConverter in uno spazio dei nomi e fornire una versione di operator == per std :: string in quello spazio dei nomi. In questo modo, quando il compilatore trova un'espressione come quella ADL (Argument Dependent Lookup) entra in gioco e tutto funziona bene.


#include <string>

namespace n1 {

class StdStringConverter
{
public:
    explicit StdStringConverter(std::string name) : m_name(name) {}
    operator std::string () { return m_name; }
private:
    std::string m_name;
};

bool operator==(std::string const& a, std::string const& b)
{
  return a == b; //EDIT: See Paul's comment on std::operator== here.
}

}

int main()
{
    using namespace n1;
    StdStringConverter converter(std::string("Me"));
    std::string name = "Me";
    return (converter == name) ? 0 : 1;   
}

Altri suggerimenti

Nel primo esempio le due classi confrontate (string e StdStringConverter) non ricevono alcun trattamento speciale dal compilatore per la conversione dei tipi. Ciò significa che il sovraccarico dell'operatore che hai fatto non viene nemmeno attivato. Il compilatore controlla l'elenco dei sovraccarichi dell'operatore == e non di essi rilevano uno StdStringConverter in modo che ti sgrida.

Nel secondo esempio il nome è char *. Poiché è un tipo primitivo, il compilatore tenta di convertire il non primitivo in un carattere *. Dal momento che hai una sostituzione in atto, funziona e confronti gli indirizzi.

Il compilatore non espliciterà il cast di tipo su operazioni che non includono tipi primitivi. Qualcosa che farà è tentare di usare i costruttori per far corrispondere i tipi. Ad esempio, se cambi il tuo primo esempio in questo:

#include <string>

class StdStringConverter
{
public:
    StdStringConverter(std::string name) : m_name(name) {}
    bool operator==(const StdStringConverter &name) { return m_name == name.m_name; }
    operator const std::string () const { return m_name; }
private:
    std::string m_name;
};

int main()
{
    StdStringConverter converter(std::string("Me"));
    const std::string name = "Me";
    // Next line causes compiler error:
    // no match for 'operator==' in 'converter == name'
    return (converter == name) ? 0 : 1;
}

Ora il programma restituisce 0. Dato che il costruttore ora non è esplicito, il compilatore tenterà di usarlo per convertire la stringa in uno StdStringConverter. Poiché ora c'è un operatore == nello StdStringConverter tutto funziona.

Esistono più fattori. Se modifichi in tal modo la dichiarazione di reso

return (std :: operator == (nome, nome))? 0: 1;

si compila, anche se ovviamente non fa la stessa cosa. D'altra parte

return (std :: operator == (convertitore, nome))? 0: 1;

non fornisce ma fornisce un messaggio di errore più interessante

nessuna funzione corrispondente per la chiamata a & # 8216; operator == (StdStringConverter & amp ;, const std :: string & amp;)

che mi ricorda che l'operatore == è modellato su basic_string < > ;, che ha tre parametri di modello da avviare. Se usi int nel tuo esempio piuttosto che std :: string, il compilatore non si lamenta.

Come ottenere l'effetto desiderato con std :: string è più intrigante ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top