Const di livello superiore non influenza la firma di una funzione

Dalla 5 ° edizione di C ++ Primer, dice:

int f(int){ /* can write to parameter */} int f(const int){ /* cannot write to parameter */} 

Le due funzioni sono indistinguibili . Ma come sai, le due funzioni differiscono molto nel modo in cui possono aggiornare i loro parametri.

Qualcuno mi può spiegare?


MODIFICARE
Penso di non aver interpretato bene la mia domanda. Quello che mi interessa davvero è perché il C ++ non consente queste due funzioni simultaneamente come funzioni diverse poiché sono molto diverse da “se il parametro può essere scritto o meno”. Intuitivamente, dovrebbe essere!


MODIFICARE
La natura del passaggio per valore è in realtà passata copiando i valori degli argomenti ai valori dei parametri . Anche per i riferimenti e i puntatori in cui i valori copiati sono indirizzi . Dal punto di vista del chiamante, se la funzione const o non-const è passata alla funzione non influenza i valori (e, naturalmente, i tipi di) copiati sui parametri.
La distinzione tra const di livello superiore e const di basso livello è importante quando si copiano oggetti. Più specificamente, const di livello superiore (non il caso di const di basso livello ) viene ignorato quando si copiano oggetti poiché la copia non influenzerà l’object copiato. È irrilevante se l’object copiato o copiato da è const o non.
Quindi per il chiamante non è necessario differenziarli. Probabilmente, dal punto di vista della funzione, i parametri const di livello superiore non influenzano l’interfaccia e / o la funzionalità della funzione. Le due funzioni effettivamente realizzano la stessa cosa. Perché preoccuparsi di implementare due copie?

consente a queste due funzioni di funzionare contemporaneamente come funzioni diverse poiché sono molto diverse rispetto a “se il parametro può essere scritto o meno”. Intuitivamente, dovrebbe essere!

Il sovraccarico delle funzioni si basa sui parametri forniti dal chiamante. Qui, è vero che il chiamante può fornire un valore const o non const , ma logicamente non dovrebbe fare alcuna differenza per la funzionalità fornita dalla funzione chiamata. Prendere in considerazione:

 f(3); int x = 1 + 2; f(x); 

Se f() fa cose diverse in ognuna di queste situazioni, sarebbe molto confuso! Il programmatore di questo codice che chiama f() può avere una ragionevole aspettativa di comportamento identico, aggiungendo o rimuovendo liberamente le variabili che passano i parametri senza invalidare il programma. Questo comportamento sicuro e sano è il punto di partenza per cui si vorrebbe giustificare le eccezioni, e in effetti ce n’è uno: i comportamenti possono essere modificati quando l’ala sovraccaricata della funzione:

 void f(const int&) { ... } void f(int&) { ... } 

Quindi, immagino che questo sia ciò che non è intuitivo: che il C ++ fornisce più “sicurezza” (comportamento coerente applicato attraverso il supporto di una singola implementazione) per i non riferimenti rispetto ai riferimenti .

Le ragioni per cui posso pensare sono:

  • Quindi, quando un programmatore sa che un parametro non const& parametro avranno una durata maggiore, possono selezionare un’implementazione ottimale. Ad esempio, nel codice qui sotto potrebbe essere più veloce restituire un riferimento a un membro T all’interno di F , ma se F è temporaneo (che potrebbe essere se il compilatore corrisponde a const F& ), allora è necessario un ritorno di valore parziale. Questo è ancora piuttosto pericoloso in quanto il chiamante deve essere consapevole del fatto che il riferimento restituito è valido solo finché il parametro è intorno.
     T f (const F &);
     T & f (F &);  // il tipo restituito potrebbe essere const e se più appropriato
  • propagazione di qualificatori come const -ness attraverso chiamate di funzioni come in:
     const T & f (const F &);
     T & f (F &);

Qui, alcune variabili (presumibilmente membro F ) di tipo T vengono esposte come const o non const sulla base della costanza del parametro quando viene chiamata f() . Questo tipo di interfaccia può essere scelto quando si desidera estendere una class con funzioni non membro (per mantenere la class minimalista, o quando si stampano modelli / alghi utilizzabili in molte classi), ma l’idea è simile alle funzioni membro const come vector::operator[]() , dove si desidera v[0] = 3 consentito su un vettore non const ma non su quello const .

Quando i valori vengono accettati in base al valore, vengono spostati al di fuori dell’ambito della funzione, in quanto la funzione restituisce, quindi non esiste uno scenario valido che implichi la restituzione di un riferimento a una parte del parametro e la volontà di propagare i suoi qualificatori.

Hacking del comportamento che si desidera

Date le regole per i riferimenti, è ansible utilizzarli per ottenere il tipo di comportamento desiderato: è sufficiente fare attenzione a non modificare il parametro di riferimento non-const-accidentalmente, quindi potrebbe voler adottare una pratica come la seguente per i parametri non const:

 T f(F& x_ref) { F x = x_ref; // or const F is you won't modify it ...use x for safety... } 

Implicazioni di ricompilazione

A parte la domanda sul perché la lingua proibisce il sovraccarico basato sulla costanza di un parametro di valore, c’è la domanda sul perché non insiste sulla coerenza della costanza nella dichiarazione e nella definizione.

Per f(const int) / f(int) … se si dichiara una funzione in un file di intestazione, è meglio NON includere il qualificatore const anche se la definizione successiva in un file di implementazione lo avrà. Questo perché durante la manutenzione il programmatore potrebbe desiderare di rimuovere il qualificatore … rimuoverlo dall’intestazione potrebbe innescare una ricompilazione inutile del codice client, quindi è meglio non insistere sulla necessità di mantenere la sincronizzazione – ed è per questo che il compilatore non lo fa t genera un errore se differiscono. Se si aggiunge o si rimuove const nella definizione della funzione, è vicino all’implementazione in cui il lettore del codice potrebbe interessarsi alla costanza durante l’analisi del comportamento della funzione. Se lo hai const sia nell’header che nel file di implementazione, il programmatore desidera renderlo non const e dimentica o decide di non aggiornare l’intestazione per evitare la ricompilazione del client, quindi è più pericoloso del contrario, dato che è ansible il programmatore avrà in mente la versione const dell’intestazione quando tenta di analizzare il codice di implementazione corrente portando a ragionamenti errati sul comportamento della funzione. Questo è tutto un problema di mantenimento molto sottile – solo veramente rilevante per la programmazione commerciale – ma questa è la base della linea guida per non usare const nell’interfaccia. Inoltre, è più conciso di ometterlo dall’interfaccia, che è più gradevole per i programmatori client che leggono sulla tua API.

Poiché non vi è alcuna differenza per il chiamante, e non esiste un modo chiaro per distinguere tra una chiamata a una funzione con un parametro const di livello superiore e una senza, le regole della lingua ignorano le conversioni di primo livello. Questo significa che questi due

 void foo(const int); void foo(int); 

sono trattati come la stessa dichiarazione. Se si fornissero due implementazioni, si otterrebbe un errore di definizione multipla.

C’è una differenza in una definizione di funzione con const di livello superiore. In uno, è ansible modificare la copia del parametro. Nell’altro, non puoi. Puoi vederlo come un dettaglio di implementazione. Per il chiamante, non c’è differenza.

 // declarations void foo(int); void bar(int); // definitions void foo(int n) { n++; std::cout << n << std::endl; } void bar(const int n) { n++; // ERROR! std::cout << n << std::endl; } 

Questo è analogo al seguente:

 void foo() { int = 42; n++; std::cout << n << std::endl; } void bar() { const int n = 42; n++; // ERROR! std::cout << n << std::endl; } 

In “The C ++ Programming Language”, quarta edizione, Bjarne Stroustrup scrive (§12.1.3):

Sfortunatamente, per preservare la compatibilità con C, un const viene ignorato al più alto livello di un tipo di argomento. Ad esempio, si tratta di due dichiarazioni della stessa funzione:

 void f(int); void f(const int); 

Quindi, a differenza di alcune delle altre risposte, questa regola del C ++ non è stata scelta a causa dell’indistinguibilità delle due funzioni, o di altre razionalità simili, ma invece come una soluzione tutt’altro che ottimale, per il bene di Compatibilità.

Infatti, nel linguaggio di programmazione D , è ansible avere questi due sovraccarichi. Tuttavia, contrariamente a quanto potrebbero suggerire altre risposte a questa domanda, il sovraccarico non costante viene preferito se la funzione viene chiamata con un valore letterale:

 void f(int); void f(const int); f(42); // calls void f(int); 

Naturalmente, è necessario fornire una semantica equivalente per i sovraccarichi, ma ciò non è specifico per questo scenario di sovraccarico, con funzioni di sovraccarico quasi indistinguibili.

Come dicono i commenti, all’interno della prima funzione il parametro potrebbe essere cambiato, se fosse stato nominato. È una copia del callee’s int. All’interno della seconda funzione, qualsiasi modifica al parametro, che è ancora una copia del numero di chiamata del chiamato, comporterà un errore di compilazione. Const è una promise che non cambierai la variabile.

Una funzione è utile solo dalla prospettiva del chiamante.

Poiché non vi è alcuna differenza per il chiamante, non vi è alcuna differenza per queste due funzioni.

Penso che l’ indistinguibile sia usato nei termini di sovraccarico e compilatore, non in termini se possono essere distinti dal chiamante.

Il compilatore non distingue tra queste due funzioni, i loro nomi sono storpiati allo stesso modo. Ciò porta alla situazione quando il compilatore tratta queste due dichiarazioni come una ridefinizione.

Rispondendo a questa parte della tua domanda:

Quello che mi interessa davvero è perché il C ++ non consente queste due funzioni simultaneamente come funzioni diverse poiché sono molto diverse da “se il parametro può essere scritto o meno”. Intuitivamente, dovrebbe essere!

Se ci pensi un po ‘di più, non è affatto intuitivo – in effetti, non ha molto senso. Come tutti gli altri hanno detto, un chiamante non viene in alcun modo influenzato quando un functon prende il suo parametro in base al valore e non gli importa nemmeno.

Ora, supponiamo per un momento che la risoluzione del sovraccarico funzionasse anche sul livello più alto. Due dichiarazioni come questa

 int foo(const int); int foo(int); 

dichiarerebbe due diverse funzioni. Uno dei problemi sarebbe quale funzione avrebbe questa espressione chiamata: foo(42) . Le regole del linguaggio potrebbero dire che i letterali sono const e che in questo caso verrà chiamato il “sovraccarico” di const. Ma questo è il minimo di un problema. Un programmatore che si sente sufficientemente male potrebbe scrivere questo:

 int foo(const int i) { return i*i; } int foo(int i) { return i*2; } 

Ora avresti due sovraccarichi che sembrano equivalenti alla semantica del chiamante ma fanno cose completamente diverse. Ora sarebbe male. Saremmo in grado di scrivere interfacce che limitano l’utente dal modo in cui fanno le cose, non da ciò che offrono.