Welche Logik steckt hinter dem Schlüsselwort "using" in C ++?

145

Welche Logik steckt hinter dem Schlüsselwort "using" in C ++?

Es wird in verschiedenen Situationen verwendet und ich versuche herauszufinden, ob all diese etwas gemeinsam haben, und es gibt einen Grund, warum das Schlüsselwort "using" als solches verwendet wird.

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
quelle
53
Standardkomitee hasst es, neue Schlüsselwörter in die C ++ - Grammatik einzuführen.
Internet besteht aus Catz
4
@tehinternetsismadeofcatz Wenn das wirklich die Logik ist, entschuldigen Sie bitte, ich werde mich jetzt umbringen.
user3111311
62
@ user3111311: Sie erkennen die Auswirkungen der Einführung neuer reservierter Wörter, oder? Dies bedeutet, dass der gesamte vorhandene Code, der diese als Bezeichnernamen verwendet hat, plötzlich nicht mehr kompiliert werden kann. Das ist eine schlechte Sache. Zum Beispiel gibt es eine Menge C-Code, der nicht als C ++ kompiliert werden kann, weil er Dinge wie enthält int class;. Es wäre noch schlimmer, wenn C ++ - Code plötzlich nicht mehr gültiges C ++ wäre.
Ben Voigt
7
@ BenVoigt: Die Tatsache, dass C-Code int class;nicht als C ++ kompiliert wird, ist keine ganz schlechte Sache. Es kann verwendet werden, um zu gewährleisten, dass C-Code als C kompiliert wird. Es ist zu leicht zu vergessen, dass C und C ++ zwei verschiedene Sprachen sind - und praktisch gibt es Code, der C und C ++ gültig ist, aber unterschiedliche Semantik aufweist.
Keith Thompson
1
In dieser Hinsicht usingist nicht schlechter (oder besser) als static. Meiner Meinung nach ist es sehr wichtig, keine neuen Schlüsselwörter einzuführen, da das Internet aus Catz und Ben Voigt besteht.
Cassio Neri

Antworten:

113

In C ++ 11 ist das usingSchlüsselwort bei Verwendung für type aliasidentisch mit typedef.

7.1.3.2

Ein typedef-Name kann auch durch eine Alias-Deklaration eingeführt werden. Der Bezeichner nach dem Schlüsselwort using wird zu einem typedef-Namen, und der optionale Attributbezeichner-seq nach dem Bezeichner gehört zu diesem typedef-Namen. Es hat dieselbe Semantik, als ob es vom typedef-Spezifizierer eingeführt worden wäre. Insbesondere definiert es keinen neuen Typ und darf nicht in der Typ-ID erscheinen.

Bjarne Stroustrup bietet ein praktisches Beispiel:

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

Vor C ++ 11 kann das usingSchlüsselwort Mitgliedsfunktionen in den Geltungsbereich bringen. In C ++ 11 können Sie dies jetzt für Konstruktoren tun (ein weiteres Beispiel von 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 liefert einen ziemlich guten Grund für die Begründung, kein neues Schlüsselwort oder keine neue Syntax einzuführen. Der Standard möchte vermeiden, dass alter Code so weit wie möglich beschädigt wird. Aus diesem Grunde ist in den Vorschlägen werden Sie Abschnitte sehen mögen Impact on the Standard, Design decisionsund wie sie könnten älteren Code beeinflussen. Es gibt Situationen, in denen ein Vorschlag eine wirklich gute Idee zu sein scheint, aber möglicherweise keine Wirkung hat, weil er zu schwierig zu implementieren, zu verwirrend oder dem alten Code zuwiderlaufen würde.


Hier ist ein altes Papier aus dem Jahr 2003 n1449 . Die Begründung scheint mit Vorlagen zu zusammenhängen. Warnung: Beim Kopieren aus PDF können Tippfehler auftreten.

Betrachten wir zunächst ein Spielzeugbeispiel:

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

Das grundlegende Problem mit dieser Redewendung und die Hauptmotivation für diesen Vorschlag besteht darin, dass die Redewendung bewirkt, dass die Vorlagenparameter in einem nicht ableitbaren Kontext erscheinen. Das heißt, es ist nicht möglich, die folgende Funktion foo aufzurufen, ohne explizit Vorlagenargumente anzugeben.

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

Die Syntax ist also etwas hässlich. Wir möchten lieber die verschachtelten vermeiden. ::type Wir würden Folgendes bevorzugen:

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

Beachten Sie, dass wir den Begriff „typedef template“ ausdrücklich vermeiden und die neue Syntax für das Paar „using“ und „=“ einführen, um Verwirrung zu vermeiden: Wir definieren hier keine Typen, sondern führen ein Synonym (dh einen Alias) für ein eine Abstraktion einer Typ-ID (dh eines Typausdrucks) mit Vorlagenparametern. Wenn die Vorlagenparameter in ableitbaren Kontexten im Typausdruck verwendet werden, können die Werte der entsprechenden Vorlagenparameter abgeleitet werden, wenn der Vorlagenalias zur Bildung einer Vorlagen-ID verwendet wird. Weitere Informationen hierzu folgen. In jedem Fall ist es jetzt möglich, generische Funktionen zu schreiben, die Vec<T>in einem ableitbaren Kontext ausgeführt werden, und die Syntax wird ebenfalls verbessert. Zum Beispiel könnten wir foo wie folgt umschreiben:

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

Wir unterstreichen hier, dass einer der Hauptgründe für das Vorschlagen von Vorlagenaliasnamen darin bestand, dass die Argumentableitung und der Aufruf zu foo(p) erfolgreich sein werden.


Das Folgepapier n1489 erklärt, warum usinganstelle von typedef:

Es wurde vorgeschlagen, das Schlüsselwort typedef (wie in diesem Artikel [4] beschrieben) (erneut) zu verwenden, um Vorlagen-Aliase einzuführen:

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

Diese Notation hat den Vorteil, dass ein Schlüsselwort verwendet wird, von dem bereits bekannt ist, dass es einen Typalias einführt. Es werden jedoch auch einige Nachteile angezeigt, darunter die Verwirrung bei der Verwendung eines Schlüsselworts, von dem bekannt ist, dass es einen Alias ​​für einen Typnamen in einem Kontext einführt, in dem der Alias ​​keinen Typ, sondern eine Vorlage angibt. Vecist kein Alias ​​für einen Typ und sollte nicht für einen typedef-Namen verwendet werden. Der Name Vecist ein Name für die Familie std::vector< [bullet] , MyAllocator< [bullet] > > - wobei das Aufzählungszeichen ein Platzhalter für einen Typnamen ist. Folglich schlagen wir die "typedef" -Syntax nicht vor. Auf der anderen Seite der Satz

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

kann gelesen / interpretiert werden als: von nun an werde ich Vec<T>als Synonym für verwenden std::vector<T, MyAllocator<T> >. Mit dieser Lesart erscheint die neue Syntax für Aliasing ziemlich logisch.

Ich denke, hier wird die wichtige Unterscheidung getroffen, alias es statt type s. Ein weiteres Zitat aus demselben Dokument:

Eine Alias-Deklaration ist eine Deklaration und keine Definition. Eine Alias-Deklaration führt einen Namen in einen deklarativen Bereich als Alias ​​für den Typ ein, der auf der rechten Seite der Deklaration angegeben ist. Der Kern dieses Vorschlags befasst sich mit Typnamen-Aliasnamen, aber die Notation kann offensichtlich verallgemeinert werden, um alternative Schreibweisen für Namespace-Aliasing oder die Benennung von überladenen Funktionen bereitzustellen (siehe ✁ 2.3 für weitere Erläuterungen). [ Mein Hinweis: In diesem Abschnitt wird erläutert, wie diese Syntax aussehen kann und warum sie nicht Teil des Vorschlags ist. ] Es kann angemerkt werden, dass die Grammatikproduktions-Alias-Deklaration überall dort akzeptabel ist, wo eine typedef-Deklaration oder eine Namespace-Alias-Definition akzeptabel ist.

Zusammenfassung für die Rolle von using:

  • Template-Aliase (oder Template-Typedefs, ersteres wird namentlich bevorzugt)
  • Namespace-Aliase (dh namespace PO = boost::program_optionsund using PO = ...gleichwertig)
  • das Dokument sagt A typedef declaration can be viewed as a special case of non-template alias-declaration. Es ist eine ästhetische Veränderung und wird in diesem Fall als identisch angesehen.
  • etwas in den Geltungsbereich bringen (zum Beispiel namespace stdin den globalen Geltungsbereich), Elementfunktionen, Konstruktoren erben

Es kann nicht verwendet werden für:

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

Tun Sie stattdessen:

using r = decltype(i);

Benennen einer Reihe von Überladungen.

// 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
quelle
2
@ user3111311 Welches andere Schlüsselwort hatten Sie im Sinn? "Auto"? "registrieren"?
Raymond Chen
2
using P = [](double)->void;ist, AFAIK, nicht gültig C ++ 11. Dies ist jedoch: using P = auto(double)->void;und erzeugt einen Funktionstyp (so dass P*es sich um einen Funktionszeiger handelt).
Dyp
2
Sein Name ist Bjarne Stroustrup;) (beachten Sie das zweite r in Stroustrup)
dyp
1
@ RaymondChen: Eigentlich registerwürde nicht so schlecht klingen, ist in:register X as Y
MFH
1
Leider registerbeginnt eine Variablendeklaration, so dass dies bereits eine Bedeutung hat. Deklarieren Sie eine Registervariable namens Y vom Typ X.
Raymond Chen