Quelle est la logique derrière le mot-clé «using» en C ++?

145

Quelle est la logique derrière le mot clé «using» en C ++?

Il est utilisé dans différentes situations et j'essaie de trouver si tous ceux-ci ont quelque chose en commun et il y a une raison pour laquelle le mot-clé "using" est utilisé comme tel.

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
user3111311
la source
53
Le comité standard déteste l'introduction de nouveaux mots-clés dans la grammaire C ++.
internets sont constitués de catz
4
@tehinternetsismadeofcatz Si c'est vraiment la logique, excusez-moi, je vais aller me tuer maintenant.
user3111311
62
@ user3111311: Vous reconnaissez les implications de l'introduction de nouveaux mots réservés, non? Cela signifie que tout le code existant qui les utilisait comme noms d'identifiant échoue soudainement à se compiler. C'est une mauvaise chose. Par exemple, il y a beaucoup de code C qui ne peut pas être compilé en C ++ car il contient des choses comme int class;. Ce serait encore pire si le code C ++ cessait soudainement d'être valide en C ++.
Ben Voigt
7
@BenVoigt: Le fait que le code C int class;ne soit pas compilé en C ++ n'est pas entièrement une mauvaise chose. Il peut être utilisé pour garantir que le code C sera compilé en C. Il est trop facile d'oublier que C et C ++ sont deux langages différents - et, en pratique, il existe du code qui est C valide et C ++ valide, mais avec une sémantique différente.
Keith Thompson
1
À cet égard, usingn'est pas pire (ou meilleur) que static. À mon humble avis, le fait de ne pas introduire de nouveaux mots-clés est très important, comme expliqué par les internets sont constitués de catz et Ben Voigt.
Cassio Neri

Réponses:

114

Dans C ++ 11, le usingmot clé utilisé pour type aliasest identique à typedef.

7.1.3.2

Un typedef-name peut également être introduit par une déclaration d'alias. L'identifiant suivant le mot-clé using devient un typedef-name et l'attribut optionnel-specifier-seq qui suit l'identificateur appartient à ce typedef-name. Il a la même sémantique que s'il avait été introduit par le spécificateur typedef. En particulier, il ne définit pas de nouveau type et il ne doit pas apparaître dans le type-id.

Bjarne Stroustrup fournit un exemple pratique:

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

Pré-C ++ 11, le usingmot - clé peut amener les fonctions membres dans la portée. En C ++ 11, vous pouvez maintenant le faire pour les constructeurs (un autre exemple de 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 fournit une assez bonne raison derrière la justification de ne pas introduire un nouveau mot-clé ou une nouvelle syntaxe. La norme veut éviter autant que possible de casser l'ancien code. Voilà pourquoi dans les documents de proposition , vous verrez des sections comme Impact on the Standard, Design decisionset comment ils pourraient affecter l' ancien code. Il y a des situations où une proposition semble être une très bonne idée mais pourrait ne pas avoir de traction parce qu'elle serait trop difficile à mettre en œuvre, trop déroutante ou contredirait l'ancien code.


Voici un vieux papier de 2003 n1449 . La justification semble être liée aux modèles. Attention: il peut y avoir des fautes de frappe en raison de la copie depuis un PDF.

Prenons d'abord un exemple de jouet:

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

Le problème fondamental de cet idiome, et le principal fait de motivation de cette proposition, est que l'idiome fait apparaître les paramètres du modèle dans un contexte non déductible. Autrement dit, il ne sera pas possible d'appeler la fonction foo ci-dessous sans spécifier explicitement les arguments de modèle.

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

Donc, la syntaxe est quelque peu moche. Nous préférerions éviter l'imbrication ::type Nous préférerions quelque chose comme ce qui suit:

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

Notez que nous évitons spécifiquement le terme «typedef template» et introduisons la nouvelle syntaxe impliquant la paire «using» et «=» pour éviter toute confusion: nous ne définissons aucun type ici, nous introduisons un synonyme (ie alias) pour une abstraction d'un type-id (c'est-à-dire une expression de type) impliquant des paramètres de modèle. Si les paramètres de modèle sont utilisés dans des contextes déductibles dans l'expression de type, chaque fois que l'alias de modèle est utilisé pour former un identifiant de modèle, les valeurs des paramètres de modèle correspondants peuvent être déduites - plus d'informations à ce sujet suivront. Dans tous les cas, il est désormais possible d'écrire des fonctions génériques qui opèrent Vec<T>dans un contexte déductible, et la syntaxe est également améliorée. Par exemple, nous pourrions réécrire foo comme:

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

Nous soulignons ici que l'une des principales raisons de proposer des alias de modèle était que la déduction d'arguments et l'appel à foo(p) réussissent.


Le document de suivi n1489 explique pourquoi usingau lieu d'utiliser typedef:

Il a été suggéré de (ré) utiliser le mot-clé typedef - comme cela est fait dans l'article [4] - pour introduire des alias de modèle:

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

Cette notation a l'avantage d'utiliser un mot-clé déjà connu pour introduire un alias de type. Cependant, il affiche également plusieurs inconvénients parmi lesquels la confusion de l'utilisation d'un mot-clé connu pour introduire un alias pour un nom-type dans un contexte où l'alias ne désigne pas un type, mais un modèle; Vecn'est pas un alias pour un type et ne doit pas être pris pour un typedef-name. Le nom Vecest un nom pour la famille std::vector< [bullet] , MyAllocator< [bullet] > > - où la puce est un espace réservé pour un nom de type. Par conséquent, nous ne proposons pas la syntaxe «typedef». D'un autre côté la phrase

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

peut être lu / interprété comme: à partir de maintenant, je vais utiliser Vec<T>comme synonyme de std::vector<T, MyAllocator<T> >. Avec cette lecture, la nouvelle syntaxe pour l'aliasing semble raisonnablement logique.

Je pense que la distinction importante est faite ici, alias es au lieu de type s. Une autre citation du même document:

Une déclaration d'alias est une déclaration et non une définition. Une déclaration d'alias introduit un nom dans une région déclarative en tant qu'alias pour le type désigné par le côté droit de la déclaration. Le cœur de cette proposition concerne les alias de nom de type, mais la notation peut évidemment être généralisée pour fournir des orthographes alternatives d'alias d'espace de noms ou d'un ensemble de dénomination de fonctions surchargées (voir ✁ 2.3 pour plus de détails). [ Ma note: cette section traite de ce à quoi cette syntaxe peut ressembler et des raisons pour lesquelles elle ne fait pas partie de la proposition. ] On peut noter que la déclaration d'alias de production de grammaire est acceptable partout où une déclaration typedef ou une définition d'alias d'espace de noms est acceptable.

Résumé, pour le rôle de using:

  • les alias de modèle (ou les typedefs de modèle, le premier étant préféré par nom)
  • alias d'espace de nom (c'est-à-dire namespace PO = boost::program_optionset using PO = ...équivalent)
  • dit le document A typedef declaration can be viewed as a special case of non-template alias-declaration. C'est un changement esthétique, et est considéré comme identique dans ce cas.
  • mettre quelque chose dans la portée (par exemple, namespace stddans la portée globale), fonctions membres, hériter des constructeurs

Il ne peut pas être utilisé pour:

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

Faites plutôt:

using r = decltype(i);

Nommer un ensemble de surcharges.

// 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);
Gabriel Staples
la source
2
@ user3111311 Quel autre mot-clé avez-vous en tête? "auto"? "S'inscrire"?
Raymond Chen
2
using P = [](double)->void;est, AFAIK, non valide C ++ 11. Ceci est cependant: using P = auto(double)->void;et produit un type de fonction (tel qu'un P*pointeur de fonction).
dyp
2
Son nom est Bjarne Stroustrup;) (notez le deuxième r dans Stroustrup)
dyp
1
@RaymondChen: ça registerne sonnerait pas si mal, est dans:register X as Y
MFH
1
Malheureusement registercommence une déclaration de variable donc cela a déjà une signification. Déclarez une variable de registre appelée Y de type X.
Raymond Chen