Qual è la logica dietro la parola chiave "using" in C ++?


145

Qual è la logica dietro la parola chiave "using" in C ++?

Viene utilizzato in diverse situazioni e sto cercando di scoprire se tutti hanno qualcosa in comune e c'è un motivo per cui la parola chiave "using" viene utilizzata come tale.

using namespace std; // to import namespace in the current namespace
using T = int; // type alias
using SuperClass::X; // using super class methods in derived class

53
Il comitato standard odia introdurre nuove parole chiave nella grammatica C ++.
Internet è fatto di Catz il

4
@tehinternetsismadeofcatz Se questa è davvero la logica, per favore scusami, andrò a uccidermi ora.
user3111311

62
@ user3111311: riconosci le implicazioni dell'introduzione di nuove parole riservate, giusto? Significa che tutto il codice esistente che ha usato quelli come nomi identificativi non riesce a compilarsi all'improvviso. Questa è una cosa cattiva. Ad esempio, c'è un sacco di codice C che non può essere compilato come C ++ perché contiene cose come int class;. Sarebbe anche peggio se il codice C ++ smettesse improvvisamente di essere C ++ valido.
Ben Voigt,

7
@BenVoigt: il fatto che l'utilizzo del codice C int class;non si compili come C ++ non è del tutto negativo. Può essere usato per garantire che il codice C sia compilato come C. È troppo facile dimenticare che C e C ++ sono due lingue diverse - e, praticamente parlando, esiste un codice che è C valido e C ++ valido, ma con semantica diversa.
Keith Thompson, il

1
A tale proposito, usingnon è peggio (o migliore) di static. IMHO il punto di non introdurre nuove parole chiave è molto importante, come spiegato da Internet è fatto di Catz e Ben Voigt.
Cassio Neri,

Risposte:


114

In C ++ 11, la usingparola chiave quando utilizzata type aliasè identica a typedef.

7.1.3.2

Un nome typedef può anche essere introdotto da una dichiarazione alias. L'identificatore che segue la parola chiave using diventa un typedef-name e l'attributo-specificifier-seq che segue l'identificatore appartiene a quel typedef-name. Ha la stessa semantica come se fosse stata introdotta dallo specificatore typedef. In particolare, non definisce un nuovo tipo e non deve apparire nell'identificativo.

Bjarne Stroustrup fornisce un esempio pratico:

typedef void (*PFD)(double);    // C style typedef to make `PFD` a pointer to a function returning void and accepting double
using PF = void (*)(double);    // `using`-based equivalent of the typedef above
using P = [](double)->void; // using plus suffix return type, syntax error
using P = auto(double)->void // Fixed thanks to DyP

Pre-C ++ 11, la usingparola chiave può portare le funzioni membro nell'ambito. In C ++ 11, ora puoi farlo per i costruttori (un altro esempio di Bjarne Stroustrup):

class Derived : public Base { 
public: 
    using Base::f;    // lift Base's f into Derived's scope -- works in C++98
    void f(char);     // provide a new f 
    void f(int);      // prefer this f to Base::f(int) 

    using Base::Base; // lift Base constructors Derived's scope -- C++11 only
    Derived(char);    // provide a new constructor 
    Derived(int);     // prefer this constructor to Base::Base(int) 
    // ...
}; 

Ben Voight fornisce una ragione abbastanza valida dietro la logica di non introdurre una nuova parola chiave o una nuova sintassi. Lo standard vuole evitare di rompere il vecchio codice il più possibile. Questo è il motivo per cui nei documenti di proposta si vedrà sezioni come Impact on the Standard, Design decisionse come potrebbero influenzare vecchio codice. Ci sono situazioni in cui una proposta sembra davvero una buona idea ma potrebbe non avere una trazione perché sarebbe troppo difficile da implementare, troppo confusa o contraddirebbe il vecchio codice.


Ecco un vecchio documento del 2003 n . 1449 . La logica sembra essere correlata ai modelli. Attenzione: potrebbero esserci errori di battitura a causa della copia da PDF.

Per prima cosa consideriamo un esempio di giocattolo:

template <typename T>
class MyAlloc {/*...*/};

template <typename T, class A>
class MyVector {/*...*/};

template <typename T>

struct Vec {
typedef MyVector<T, MyAlloc<T> > type;
};
Vec<int>::type p; // sample usage

Il problema fondamentale con questo linguaggio, e il principale fatto motivante per questa proposta, è che il linguaggio fa apparire i parametri del modello in un contesto non deducibile. Cioè, non sarà possibile chiamare la funzione foo sotto senza specificare esplicitamente gli argomenti del template.

template <typename T> void foo (Vec<T>::type&);

Quindi, la sintassi è piuttosto brutta. Preferiremmo evitare il nidificato ::type Preferiremmo qualcosa di simile al seguente:

template <typename T>
using Vec = MyVector<T, MyAlloc<T> >; //defined in section 2 below
Vec<int> p; // sample usage

Si noti che evitiamo in modo specifico il termine "modello typedef" e introduciamo la nuova sintassi che coinvolge la coppia "using" e "=" per evitare confusione: non stiamo definendo alcun tipo qui, stiamo introducendo un sinonimo (ovvero alias) per un'astrazione di un id-tipo (cioè espressione di tipo) che coinvolge parametri di template. Se i parametri del modello vengono utilizzati in contesti deducibili nell'espressione del tipo, ogni volta che viene utilizzato l'alias del modello per formare un ID modello, i valori dei parametri del modello corrispondente possono essere dedotti - ne seguiranno altri. In ogni caso, ora è possibile scrivere funzioni generiche che operano Vec<T>in un contesto deducibile e anche la sintassi è migliorata. Ad esempio, potremmo riscrivere foo come:

template <typename T> void foo (Vec<T>&);

Sottolineiamo qui che uno dei motivi principali per proporre alias modello è stato che la deduzione degli argomenti e la chiamata a foo(p) riuscissero.


Il documento di follow-up n1489 spiega perché usinginvece di usare typedef:

È stato suggerito di (ri) usare la parola chiave typedef - come fatto nel documento [4] - per introdurre alias di template:

template<class T> 
    typedef std::vector<T, MyAllocator<T> > Vec;

Tale notazione ha il vantaggio di utilizzare una parola chiave già nota per introdurre un alias di tipo. Tuttavia, mostra anche diversi svantaggi tra cui la confusione dell'uso di una parola chiave nota per introdurre un alias per un tipo-nome in un contesto in cui l'alias non designa un tipo, ma un modello; Vecnon è un alias per un tipo e non dovrebbe essere preso per un nome typedef. Il nome Vecè un nome per la famiglia std::vector< [bullet] , MyAllocator< [bullet] > > , in cui il punto elenco è un segnaposto per un tipo-nome. Di conseguenza non proponiamo la sintassi "typedef". D'altra parte la frase

template<class T>
    using Vec = std::vector<T, MyAllocator<T> >;

può essere letto / interpretato come: da ora in poi, userò Vec<T>come sinonimo di std::vector<T, MyAllocator<T> >. Con quella lettura, la nuova sintassi per l'aliasing sembra ragionevolmente logica.

Penso che l'importante distinzione sia fatta qui, alias es invece del tipo s. Un'altra citazione dallo stesso documento:

Una dichiarazione alias è una dichiarazione e non una definizione. Una dichiarazione alias introduce un nome in una regione dichiarativa come alias per il tipo designato dal lato destro della dichiarazione. Il nucleo di questa proposta riguarda gli alias dei nomi di tipo, ma la notazione può ovviamente essere generalizzata per fornire ortografia alternativa di alias dello spazio dei nomi o set di nomi di funzioni sovraccariche (vedere ✁ 2.3 per ulteriori discussioni). [ Nota mia: in questa sezione vengono illustrati l'aspetto della sintassi e i motivi per cui non fa parte della proposta. ] Si può notare che la dichiarazione di alias di produzione grammaticale è accettabile ovunque sia accettabile una dichiarazione di battitura a macchina o una definizione di alias di spazio dei nomi.

Riepilogo, per il ruolo di using:

  • alias modello (o typedef modello, il primo è preferito per nome)
  • alias namespace (cioè, namespace PO = boost::program_optionse using PO = ...equivalenti)
  • dice il documento A typedef declaration can be viewed as a special case of non-template alias-declaration. È un cambiamento estetico ed è considerato identico in questo caso.
  • portando qualcosa nel campo di applicazione (ad esempio, namespace stdnel campo di applicazione globale), funzioni membro, ereditando costruttori

Non può essere utilizzato per:

int i;
using r = i; // compile-error

Invece fai:

using r = decltype(i);

Denominare una serie di sovraccarichi.

// bring cos into scope
using std::cos;

// invalid syntax
using std::cos(double);

// not allowed, instead use Bjarne Stroustrup function pointer alias example
using test = std::cos(double);

2
@ user3111311 Quale altra parola chiave avevi in ​​mente? "auto"? "Registrati"?
Raymond Chen, il

2
using P = [](double)->void;è, AFAIK, non valido C ++ 11. Questo tuttavia è: using P = auto(double)->void;e produce un tipo di funzione (tale che P*è un puntatore a funzione).
dyp,

2
Il suo nome è Bjarne Stroustrup;) (nota la seconda r in Stroustrup)
dyp

1
@RaymondChen: in realtà registernon suonerebbe così male, è in:register X as Y
MFH,

1
Sfortunatamente registerinizia una dichiarazione variabile quindi questo ha già un significato. Dichiarare una variabile di registro chiamata Y di tipo X.
Raymond Chen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.