Pagina didattica di G. Servizi
Home >> Linguaggio C++ >> vocabolario C++


livello medio-alto

auto

Completamente disusata e implicata dalla sua stessa assenza prima dell'avvento dello standard 2011, quest'ultimo le ha attribuito una nuova dignità; agli albori del linguaggio (ANSI C) serviva a contrapporsi alla parola static, ma ben presto ogni variabile non esplicitamente qualificata si sottintese qualificata come auto. In pratica, in ogni contesto ove potessero incontrarsi, le due seguenti dichiarazioni:

auto int ciccio;
e
int ciccio;

erano totalmente equivalenti prima del nuovo standard, mentre nel nuovo standard la prima è SBAGLIATA.

In effetti il c++11 prevede l'uso di auto per effettuare la dichiarazione di una variabile lasciando al compilatore l'onere di distinguerne il tipo, che, pertanto, non deve essere specificato. Affinché il compilatore possa capire di quale tipo si tratti si può sfruttare una delle seguenti situazioni:

  • la variabile è inizializzata contestualmente alla dichiarazione:
    auto n = 12; // n è di tipo int

  • il tipo è dedotto in base al risultato di un'operazione; ciò è particolarmente utile specialmente nel caso di una funzione generica nella quale non sia possibile, al momento in cui viene scritta dal programmatore, decidere quale tipo assegnare a una variabile che dipenda dai tipi generici, come nel seguente esempio:
    template <typename X, typename Y> void funza(X x, Y y)
    {
    // ... omissis
    s = x * y; }

    Come dovrebbe essere dichiarata la variabile s? Appartiene al tipo X, al tipo Y o a nessuno dei due, magari perché è stato effettuato l'overload dell'operatore * per tutte le istanziazioni del tipo X?

    La soluzione di questa ambiguità è auto e la precedente funzione va scritta così:

    template <typename X, typename Y> void funza(X x, Y y)
    {
    // ... omissis
    auto s = x * y; }

  • il tipo è dedotto dal fatto che si stia dichiarando una variabile che appartenga a una classe contenuta in un'altra già dichiarata, come ad esempio quando si dichiara un iteratore di una standard template class.

    vector <double> x;
    /*vector <double> :: iterator i = x . begin( ); */
    // si sostituisce, nel nuovo standard, semplicemente con
    auto i = x . begin( );

    Anche senza voler ricorrere alla standard template library, è sufficiente il seguente esempio elementare:

    struct Ciccio
    {
    struct Pappo
    {Pappo(  ) {  }
    };
    Pappo p;
    Ciccio(  ) {  }
    Pappo funza(  ) {return p;}
    };

    int main(  )
    {
    Ciccio c;
    auto u = c . funza(  );
    // invece di
    // Ciccio :: Pappo u = c . funza(  );
    }

  • il tipo è dedotto dal fatto che si stia dichiarando una funzione che presenta la clausola del cosiddetto trailing return type, vale a dire quanto specificato a tal proposito nella pagina dedicata alla parola decltype.

  • NON È LECITO usare questa parola per lasciar dedurre al compilatore il tipo di un array, come in questa linea di codice ERRATA:

    auto u[  ] = {1, 2, 3};

    nella quale si pretenderebbe che il compilatore capisca, sulla base degli inizializzatori, che u sia un array di tre int. La ragione dell'errore dovrebbe apparire chiara a chi proseguirà la lettura di questo stesso documento addentrandosi nel livello alto. Per chi non lo facesse basti sapere che NON SI FA.

Estensioni allo standard introdotte nel 2014

  • il tipo è dedotto dal fatto che si stia dichiarando una funzione ANCHE SENZA la clausola decltype citata nell'ultimo punto dell'elenco precedente, e basandosi sul tipo dell'espressione coinvolta nella/nelle istruzione/i return della funzione stessa (COERENTI FRA LORO se più di una sola).

    Esempio:

    auto funza(  ) {/*omissis*/ return .......;}
    // funza ha come tipo quello dell'espressione che si trova
    // al posto dei puntini.

  • il tipo è dedotto tramite la clausola decltype(auto), colà discussa, sia nella dichiarazione di variabili sia in quella di funzioni.


livello alto

Questioni fini:

La regola di fondo usata dal compilatore per la deduzione del tipo mediante l'uso della parola auto si basa sullo stesso principio utilizzato nella deduzione dei tipi templatizzati di una funzione template all'atto di una sua istanziazione.

Ciò significa che, in una dichiarazione come auto i = 0; il tipo viene dedotto come int perché la stessa cosa accadrebbe a qualsiasi funzione template, come ad esempio

template <typename X> void funza(X x);

che fosse istanziata in un'espressione come funza(0).
La cosa acquista maggior rilievo quando auto fosse accompagnata da altri qualificatori: ad esempio, nella dichiarazione const auto & i = espressione; il tipo dedotto è quello dell'istanziazione di template <typename X> void funza(const X& x); con l'invocazione funza(espressione);. E a quel punto occorre che l'inizializzatore espressione sia un qualche lvalue costante.

Altresì va osservato che, in una dichiarazione in cui compaia auto &&, a causa della regola sul collasso dei segni '&' (cfr. move semantic), il tipo dedotto può essere tanto un riferimento destro quanto un riferimento sinistro, secondo la categoria cui appartiene l'inizializzatore (GRAN LINGUAGGIO).

Timete:

Quando si usa la prima delle due estensioni introdotte dallo standard 2014, non è lecito per la funzione dichiaranda avere un oggetto initializer_list nel proprio return: una tale funzione dovrà fuggire da questo genere di dichiarazione.

Altresì è vietata questa forma di dichiarazione per i metodi di una classe qualificati con la parola virtual.

Invece è tranquillamente utilizzabile per le funzioni template, anche in overload, come appare ovvio, visto quanto si è detto sopra, ma, meno ovviamente, ANCHE se l'espressione in return dovesse essere templatizzata: la deduzione del tipo avviene, come appare altrettanto ovvio, SOLO all'atto dell'istanziazione.

Esempio:

template <typename X> auto funza(X x) {return x;} // primo overload
template <typename X> auto funza(X* x) {return *x;} // secondo overload
typedef decltype(funza(1)) funza_int; // istanziato il primo overload
// funza_int è il tipo di funza<int>, quindi int


Etiam timete:

Se si dichiara una funzione col metodo indicato nella prima estensione 2014, POI NON LA SI PUÒ PIÙ RIDICHIARARE, diversamente da come accade ordinariamente, NEPPURE se la ridichiarazione fosse COERENTE col tipo dedotto nella prima istanziazione. Per intendersi in quanto segue sono commentate le linee che genererebbero ERRORE:

auto funza(  ); // semplice dichiarazione; accettata. Tipo NON deducibile.
auto funza(  ) {return 1;} // definizione della precedente. Tipo dedotto: int
// int funza(  ); // ERRORE: ridichiarata, anche se COERENTE
auto funza(  ); // mera ridichiarazione IDENTICA; accettata.
//decltype(1) funza(  ); // ERRORE: ridichiarata con deduzione diversa


Quanto sopra esposto acquista maggior rilievo se le dichiarazioni avvengono in ambiti differenti. Ad esempio nel seguente codice, PERFETTAMENTE CORRETTO,

template <typename X> class Pappo {
friend X friend_funza(X);
// omissis
};

auto friend_funza(int i) {return i;}

NON SI PENSI NEPPURE PER UN NANOSECONDO che la auto friend_funza(int) definita nell'ultima riga, nonostante abbia int come tipo dedotto, sia la funzione dichiarata friend di un'eventuale istanza di Pappo<int>.

Va infine osservato che quando auto compare nella clausola decltype(auto) NON DEVONO ESSERE AGGIUNTI QUALIFICATORI DI SORTA, vale a dire che è ERRORE scrivere qualcosa come decltype(auto) & i = (x); in sostanza decltype(auto) basta a sé stesso.

Torna in cima alla pagina