puntatori - static_cast c++ ita



Perché è consentita la conversione implicita da const a non-const? (1)

Perché C ++ consente la compilazione del seguente codice?

std::unordered_map<std::string, int> m;
// ...
for (const std::pair<std::string, int>& p: m)
{
    // ...
}

Secondo Effective Modern C ++ di Scott Meyers (pagina 40-41):

[...] la parte fondamentale di una std::unordered_map è const , quindi il tipo di std::pair nella tabella hash (che è ciò che una std::unordered_map è) non è std::pair<std::string, int> , è std::pair <const std::string, int> . Ma non è il tipo dichiarato per la variabile p nel ciclo sopra. Di conseguenza, i compilatori cercheranno di trovare un modo per convertire gli oggetti std::pair<const std::string, int> (cioè, cosa c'è nella tabella hash) in oggetti std::pair<std::string, int> (il tipo dichiarato per p ). Avranno successo creando un oggetto temporaneo del tipo a cui p vuole legare copiando ciascun oggetto in m , quindi legando il riferimento p a quell'oggetto temporaneo. Alla fine di ogni iterazione del ciclo, l'oggetto temporaneo verrà distrutto. Se hai scritto questo ciclo, probabilmente sarai sorpreso da questo comportamento, perché quasi sicuramente vorrai legare semplicemente il riferimento p a ciascun elemento in m .

Qual è il vantaggio di consentire questa conversione implicita? C'è qualche caso di uso comune in cui lo sviluppatore si aspetta / preferisce questa conversione implicita (piuttosto che ottenere un errore del compilatore)?


Un compilatore conforme agli standard dovrebbe "vedere" il ciclo for come segue:

auto&& __range = m; 
for (auto __begin = std::begin(m), __end = std::end(m); __begin != __end; ++__begin) { 
    const std::pair<std::string, int>& p = *__begin;
    //loop_statement 
}

Che fondamentalmente riassume la tua domanda sul perché è permesso il seguente codice:

std::pair<std::string, int> p = std::pair<const std::string, int>{};

Nota che ho eliminato la const& parte di p , perché non è rilevante. La conversione è la stessa, l'unica differenza è che il temporaneo è associato a un riferimento anziché essere copiato.

Se ti stai chiedendo perché lo snippet di OP non funziona con un riferimento non const, la conversione è il motivo per cui. Il risultato della conversione è un oggetto temporaneo e poiché qualsiasi modifica al temporaneo sarà inutile (la sua durata non viene estesa e quindi viene distrutta subito dopo), quindi la lingua non la consente.

Questo è permesso perché std::pair ha un costruttore che abilita questa conversione.

template< class U1, class U2 >
pair( const pair<U1, U2>& p );

Nel tuo caso, U1 è dedotto come const std::string e U2 come int . In realtà non importa quali sono i qualificatori di cv U1 e U2 , perché gli elementi di p vengono copiati.

I benefici sono gli stessi del perché questo è permesso:

const int zero{};
int zero2 = zero;

Ad esempio, considera il seguente esempio non realistico:

struct {
    std::pair<int, int> pos;
} player;

std::pair<const int, const int> treasure{1, 2}; // position of treasure
player.pos = treasure; // ok

Ora cosa succede se, come dici tu, questa conversione non è stata accettata per qualche motivo. Cosa dovrebbe fare il programmatore?

player.pos.first = treasure.first;
player.pos.second = treasure.second;

Se anche questo non fosse consentito, non sarebbe consentito anche il caso con gli zeri di cui sopra, il che non ha senso, perché stai copiando zero , quindi non dovrebbe importare se puoi modificarlo o meno, perché è un'operazione completamente diversa.

Se questo è permesso, allora perchè player.pos = treasure; essere negato, perché l'unica cosa che fa è copiare? Come sopra, non dovrebbe importare se puoi cambiare gli elementi del treasure , perché li stai solo copiando.

Questo è anche il motivo per cui dovresti usare auto&& o const auto& per cicli a distanza (e forse anche in generale?) Perché può evitare una copia se non stai attento.





const