Namespaces
Variants

Using-declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Führt einen Namen, der an anderer Stelle definiert ist, in den deklarativen Bereich ein, in dem diese using-Deklaration erscheint. Siehe using enum und (since C++20) using namespace für andere verwandte Deklarationen.

using typename (optional) nested-name-specifier unqualified-id ; (bis C++17)
using declarator-list ; (seit C++17)
typename - das Schlüsselwort typename kann nach Bedarf verwendet werden, um abhängige Namen aufzulösen, wenn die using-Deklaration einen Member-Typ aus einer Basisklasse in eine Klassen-Template einführt
nested-name-specifier - eine Folge von Namen und Bereichsauflösungsoperatoren :: , die mit einem Bereichsauflösungsoperator endet. Ein einzelnes :: bezieht sich auf den globalen Namensraum.
unqualified-id - ein Id-Ausdruck
declarator-list - kommagetrennte Liste von einem oder mehreren Deklaratoren der Form typename (optional) nested-name-specifier unqualified-id . Einige oder alle Deklaratoren können mit einer Auslassungspunktefolge ... versehen sein, um eine Paketentfaltung anzuzeigen

Inhaltsverzeichnis

Erklärung

Using-Deklarationen können verwendet werden, um Namespace-Mitglieder in andere Namespaces und Blockbereiche einzuführen, oder um Basisklassenmitglieder in abgeleitete Klassendefinitionen einzuführen , oder um Enumeratoren in Namespaces, Block- und Klassenbereiche einzuführen (since C++20) .

Eine using-Deklaration mit mehr als einem using-Deklarator entspricht einer entsprechenden Folge von using-Deklarationen mit jeweils einem using-Deklarator.

(since C++17)

Im Namespace- und Blockbereich

Using-declarations führen ein Mitglied eines anderen Namespace in den aktuellen Namespace oder Blockbereich ein.

#include <iostream>
#include <string>
using std::string;
int main()
{
    string str = "Beispiel";
    using std::cout;
    cout << str;
}

Siehe namespace für Details.

In der Klassendefinition

Die using-Deklaration führt ein Mitglied einer Basisklasse in die abgeleitete Klassendefinition ein, beispielsweise um ein geschütztes Mitglied der Basis als öffentliches Mitglied der abgeleiteten Klasse verfügbar zu machen. In diesem Fall muss der nested-name-specifier eine Basisklasse der gerade definierten Klasse benennen. Wenn der Name der Name einer überladenen Memberfunktion der Basisklasse ist, werden alle Basisklassen-Memberfunktionen mit diesem Namen eingeführt. Wenn die abgeleitete Klasse bereits ein Mitglied mit demselben Namen, derselben Parameterliste und denselben Qualifikationen hat, verdeckt oder überschreibt das abgeleitete Klassenmitglied das aus der Basisklasse eingeführte Mitglied (steht nicht im Konflikt damit).

#include <iostream>
struct B
{
    virtual void f(int) { std::cout << "B::f\n"; }
    void g(char)        { std::cout << "B::g\n"; }
    void h(int)         { std::cout << "B::h\n"; }
protected:
    int m; // B::m ist protected
    typedef int value_type;
};
struct D : B
{
    using B::m;          // D::m ist public
    using B::value_type; // D::value_type ist public
    using B::f;
    void f(int) override { std::cout << "D::f\n"; } // D::f(int) überschreibt B::f(int)
    using B::g;
    void g(int) { std::cout << "D::g\n"; } // sowohl g(int) als auch g(char) sind sichtbar
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) versteckt B::h(int)
};
int main()
{
    D d;
    B& b = d;
//  b.m = 2;  // Fehler: B::m ist protected
    d.m = 1;  // protected B::m ist als public D::m zugreifbar
    b.f(1);   // ruft abgeleitetes f() auf
    d.f(1);   // ruft abgeleitetes f() auf
    std::cout << "----------\n";
    d.g(1);   // ruft abgeleitetes g(int) auf
    d.g('a'); // ruft Basis g(char) auf, verfügbar gemacht via using B::g;
    std::cout << "----------\n";
    b.h(1);   // ruft Basis h() auf
    d.h(1);   // ruft abgeleitetes h() auf
}

Ausgabe:

D::f
D::f
----------
D::g
B::g
----------
B::h
D::h

Vererbende Konstruktoren

Wenn die using-Deklaration auf einen Konstruktor einer direkten Basis der definierten Klasse verweist (z.B. using Base :: Base ; ), werden alle Konstruktoren dieser Basis (unter Ignorierung des Member-Zugriffs) für die Überlagerungsauflösung bei der Initialisierung der abgeleiteten Klasse sichtbar gemacht.

Wenn die Überladungsauflösung einen geerbten Konstruktor auswählt, ist er zugreifbar, wenn er zugreifbar wäre, wenn er zum Konstruieren eines Objekts der entsprechenden Basisklasse verwendet würde: Die Zugreifbarkeit der using-Deklaration, die ihn eingeführt hat, wird ignoriert.

Wenn die Überladungsauflösung beim Initialisieren eines Objekts einer solchen abgeleiteten Klasse einen der geerbten Konstruktoren auswählt, dann wird das Base -Subobjekt, von dem der Konstruktor geerbt wurde, mit dem geerbten Konstruktor initialisiert, und alle anderen Basen und Member von Derived werden wie durch den standardmäßig generierten Standardkonstruktor initialisiert (Standard-Member-Initialisierer werden verwendet, falls vorhanden, andernfalls findet Standardinitialisierung statt). Die gesamte Initialisierung wird als einzelner Funktionsaufruf behandelt: Die Initialisierung der Parameter des geerbten Konstruktors ist sequenced before der Initialisierung einer beliebigen Basis oder eines Members des abgeleiteten Objekts.

struct B1 { B1(int, ...) {} };
struct B2 { B2(double)   {} };
int get();
struct D1 : B1
{
    using B1::B1; // erbt B1(int, ...)
    int x;
    int y = get();
};
void test()
{
    D1 d(2, 3, 4); // OK: B1 wird durch Aufruf von B1(2, 3, 4) initialisiert,
                   // dann wird d.x standardinitialisiert (keine Initialisierung erfolgt),
                   // dann wird d.y durch Aufruf von get() initialisiert
    D1 e;          // Fehler: D1 hat keinen Standardkonstruktor
}
struct D2 : B2
{
    using B2::B2; // erbt B2(double)
    B1 b;
};
D2 f(1.0); // Fehler: B1 hat keinen Standardkonstruktor
struct W { W(int); };
struct X : virtual W
{
    using W::W; // erbt W(int)
    X() = delete;
};
struct Y : X
{
    using X::X;
};
struct Z : Y, virtual W
{
    using Y::Y;
};
Z z(0); // OK: Initialisierung von Y ruft nicht den Standardkonstruktor von X auf

Wenn das Base -Basisklassen-Subobjekt nicht als Teil des Derived -Objekts initialisiert werden soll (d.h. Base ist eine virtuelle Basisklasse von Derived , und das Derived -Objekt ist nicht das am meisten abgeleitete Objekt ), wird der Aufruf des geerbten Konstruktors, einschließlich der Auswertung aller Argumente, ausgelassen:

struct V
{
    V() = default;
    V(int);
};
struct Q { Q(); };
struct A : virtual V, Q
{
    using V::V;
    A() = delete;
};
int bar() { return 42; }
struct B : A
{
    B() : A(bar()) {} // OK
};
struct C : B {};
void foo()
{
    C c; // "bar" wird nicht aufgerufen, da das V-Subobjekt
         // nicht als Teil von B initialisiert wird
         // (das V-Subobjekt wird als Teil von C initialisiert,
         //  da "c" das am stärksten abgeleitete Objekt ist)
}

Wenn der Konstruktor von mehreren Basisklassen-Subobjekten des Typs Base geerbt wurde, ist das Programm fehlerhaft, ähnlich wie mehrfach geerbte nicht-statische Memberfunktionen:

struct A { A(int); };
struct B : A { using A::A; };
struct C1 : B { using B::B; };
struct C2 : B { using B::B; };
struct D1 : C1, C2
{
    using C1::C1;
    using C2::C2;
};
D1 d1(0); // ungültig: Konstruktor von verschiedenen B-Basis-Subobjekten geerbt
struct V1 : virtual B { using B::B; };
struct V2 : virtual B { using B::B; };
struct D2 : V1, V2
{
    using V1::V1;
    using V2::V2;
};
D2 d2(0); // OK: Es gibt nur ein B-Subobjekt.
          // Dies initialisiert die virtuelle B-Basisklasse,
          //   welche die A-Basisklasse initialisiert
          // und initialisiert dann die V1- und V2-Basisklassen
          //   wie durch einen standardmäßigen Standardkonstruktor

Wie bei using-Deklarationen für andere nicht-statische Memberfunktionen, wenn ein geerbter Konstruktor mit der Signatur eines der Konstruktoren von Derived übereinstimmt, wird er von der in Derived gefundenen Version von der Suche ausgeschlossen. Wenn einer der geerbten Konstruktoren von Base zufällig die Signatur besitzt, die mit einem Kopier-/Verschiebekonstruktor von Derived übereinstimmt, verhindert dies nicht die implizite Generierung des Derived Kopier-/Verschiebekonstruktors (welcher dann die geerbte Version verdeckt, ähnlich wie bei using operator= ).

struct B1 { B1(int); };
struct B2 { B2(int); };
struct D2 : B1, B2
{
    using B1::B1;
    using B2::B2;
    D2(int); // OK: D2::D2(int) verdeckt sowohl B1::B1(int) als auch B2::B2(int)
};
D2 d2(0);    // ruft D2::D2(int) auf

Innerhalb einer templated class , wenn eine using-Deklaration auf einen dependent name verweist, wird dies als Benennung eines Konstruktors betrachtet, falls der nested-name-specifier einen terminalen Namen hat, der mit dem unqualified-id übereinstimmt.

template<class T>
struct A : T
{
    using T::T; // OK, erbt Konstruktoren von T
};
template<class T, class U>
struct B : T, A<U>
{
    using A<U>::A; // OK, erbt Konstruktoren von A<U>
    using T::A;    // erbt keine Konstruktoren von T
                   // selbst wenn T eine Spezialisierung von A<> sein könnte
};
(seit C++11)


Einführung von Scoped-Enumeratoren

Zusätzlich zu Membern eines anderen Namespace und Membern von Basisklassen kann eine using-Deklaration auch Enumeratoren von Enumerationen in Namespace-, Block- und Klassenbereiche einführen.

Eine using-Deklaration kann auch mit unscoped-Enumeratoren verwendet werden.

enum class button { up, down };
struct S
{
    using button::up;
    button b = up; // OK
};
using button::down;
constexpr button non_up = down; // OK
constexpr auto get_button(bool is_up)
{
    using button::up, button::down;
    return is_up ? up : down; // OK
}
enum unscoped { val };
using unscoped::val; // OK, though needless
(seit C++20)

Hinweise

Nur der ausdrücklich in der using-Deklaration genannte Name wird in den deklarativen Bereich übertragen: Insbesondere werden Enumeratoren nicht übertragen, wenn der Enumerationstypname mittels using-Deklaration eingeführt wird.

Eine using-Deklaration kann nicht auf einen Namespace verweisen , auf einen scoped enumerator (bis C++20) , auf einen Destruktor einer Basisklasse oder auf eine Spezialisierung eines Member-Templates für eine benutzerdefinierte Konvertierungsfunktion.

Eine using-Deklaration kann keine Spezialisierung einer Member-Template benennen ( template-id ist durch die Grammatik nicht erlaubt):

struct B
{
    template<class T>
    void f();
};
struct D : B
{
    using B::f;      // OK: benennt ein Template
//  using B::f<int>; // Fehler: benennt eine Template-Spezialisierung
    void g() { f<int>(); }
};

Eine using-Deklaration kann ebenfalls nicht verwendet werden, um den Namen eines abhängigen Member-Templates als einen Template-Namen einzuführen (der template -Disambiguator für abhängige Namen ist nicht erlaubt).

template<class X>
struct B
{
    template<class T>
    void f(T);
};
template<class Y>
struct D : B<Y>
{
//  using B<Y>::template f; // Fehler: Disambiguator nicht erlaubt
    using B<Y>::f;          // kompiliert, aber f ist kein Template-Name
    void g()
    {
//      f<int>(0);          // Fehler: f ist nicht als Template-Name bekannt,
                            // daher beginnt < keine Template-Argumentliste
        f(0);               // OK
    }   
};

Wenn eine using-Deklaration den Zuweisungsoperator der Basisklasse in die abgeleitete Klasse bringt, dessen Signatur zufällig mit dem Kopierzuweisungs- oder Verschiebezuweisungsoperator der abgeleiteten Klasse übereinstimmt, wird dieser Operator durch den implizit deklarierten Kopier-/Verschiebezuweisungsoperator der abgeleiteten Klasse verdeckt. Gleiches gilt für eine using-Deklaration, die einen Basisklassenkonstruktor erbt, der zufällig mit dem Kopier-/Verschiebekonstruktor der abgeleiteten Klasse übereinstimmt (seit C++11) .

Die Semantik von geerbten Konstruktoren wurde nachträglich durch einen Defect Report gegen C++11 geändert. Zuvor verursachte eine deklarierende Konstruktorvererbung eine Reihe von synthetisierten Konstruktordeklarationen, die in die abgeleitete Klasse injiziert wurden, was redundante Argumentkopien/-verschiebungen verursachte, problematische Interaktionen mit einigen Formen von SFINAE hatte und in einigen Fällen auf wichtigen ABIs nicht implementierbar sein konnte. Ältere Compiler können weiterhin die vorherige Semantik implementieren.

Alte Semantik geerbter Konstruktoren

Wenn die using-Deklaration auf einen Konstruktor einer direkten Basis der definierten Klasse verweist (z.B. using Base :: Base ; ), werden Konstruktoren dieser Basisklasse gemäß den folgenden Regeln geerbt:

1) Eine Menge von Kandidaten für geerbte Konstruktoren wird zusammengesetzt aus
a) Allen Nicht-Template-Konstruktoren der Basisklasse (nach Weglassen von Ellipsenparametern, falls vorhanden) (seit C++14)
b) Für jeden Konstruktor mit Standardargumenten oder Ellipsenparameter, allen Konstruktorsignaturen, die durch Weglassen der Ellipse und Auslassung von Standardargumenten vom Ende der Argumentlisten nacheinander gebildet werden
c) Allen Konstruktortemplates der Basisklasse (nach Weglassen von Ellipsenparametern, falls vorhanden) (seit C++14)
d) Für jedes Konstruktortemplate mit Standardargumenten oder Ellipse, allen Konstruktorsignaturen, die durch Weglassen der Ellipse und Auslassung von Standardargumenten vom Ende der Argumentlisten nacheinander gebildet werden
2) Alle Kandidaten für geerbte Konstruktoren, die nicht der Standardkonstruktor oder Kopier-/Verschiebekonstruktor sind und deren Signaturen nicht mit benutzerdefinierten Konstruktoren in der abgeleiteten Klasse übereinstimmen, werden implizit in der abgeleiteten Klasse deklariert. Die Standardparameter werden nicht geerbt:
struct B1
{
    B1(int);
};
struct D1 : B1
{
    using B1::B1;
    // The set of candidate inherited constructors is 
    // 1. B1(const B1&)
    // 2. B1(B1&&)
    // 3. B1(int)
    // D1 has the following constructors:
    // 1. D1() = delete
    // 2. D1(const D1&) 
    // 3. D1(D1&&)
    // 4. D1(int) <- inherited
};
struct B2
{
    B2(int = 13, int = 42);
};
struct D2 : B2
{
    using B2::B2;
    // The set of candidate inherited constructors is
    // 1. B2(const B2&)
    // 2. B2(B2&&)
    // 3. B2(int = 13, int = 42)
    // 4. B2(int = 13)
    // 5. B2()
    // D2 has the following constructors:
    // 1. D2()
    // 2. D2(const D2&)
    // 3. D2(D2&&)
    // 4. D2(int, int) <- inherited
    // 5. D2(int) <- inherited
};

Die geerbten Konstruktoren entsprechen benutzerdefinierten Konstruktoren mit einem leeren Körper und einer Member-Initialisierungsliste , die aus einem einzelnen nested-name-specifier besteht, der alle seine Argumente an den Basisklassenkonstruktor weiterleitet.

Es hat dieselbe Zugriffsebene wie der entsprechende Basisklassenkonstruktor. Es ist constexpr , wenn der benutzerdefinierte Konstruktor die constexpr -Konstruktoranforderungen erfüllt hätte. Es ist gelöscht, wenn der entsprechende Basisklassenkonstruktor gelöscht ist oder wenn ein standardmäßiger Standardkonstruktor gelöscht wäre (außer dass der Aufbau der Basis, deren Konstruktor geerbt wird, nicht zählt). Ein erbender Konstruktor kann nicht explizit instanziiert oder spezialisiert werden.

Wenn zwei using-Deklarationen den Konstruktor mit derselben Signatur vererben (von zwei direkten Basisklassen), ist das Programm fehlerhaft.

Ein erbendes Konstruktortemplate sollte nicht explizit instanziiert oder explizit spezialisiert werden.

(seit C++11)

Pack expansions in using-declarations ermöglichen es, eine Klasse zu bilden, die überladene Member variadischer Basen ohne Rekursion verfügbar macht:

template<typename... Ts>
struct Overloader : Ts...
{
    using Ts::operator()...; // exposes operator() from every base
};
template<typename... T>
Overloader(T...) -> Overloader<T...>; // C++17 deduction guide, not needed in C++20
int main()
{
    auto o = Overloader{ [] (auto const& a) {std::cout << a;},
                         [] (float f) {std::cout << std::setprecision(3) << f;} };
}
(seit C++17)
Feature-Test-Makro Wert Std Feature
__cpp_inheriting_constructors 200802L (C++11) Vererbende Konstruktoren
201511L (C++17)
(DR11)
Neuformulierung vererbender Konstruktoren
__cpp_variadic_using 201611L (C++17) Pack-Erweiterungen in using -Deklarationen

Schlüsselwörter

using

Fehlerberichte

Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR Angewendet auf Verhalten wie veröffentlicht Korrektes Verhalten
CWG 258 C++98 eine nicht-konstante Memberfunktion einer abgeleiteten Klasse kann
eine konstante Memberfunktion ihrer Basis überschreiben und/oder verbergen
Überschreiben und Verbergen erfordern ebenfalls
gleiche CV-Qualifizierungen
CWG 1738 C++11 es war unklar, ob es erlaubt ist,
Spezialisierungen von vererbten Konstruktor-Templates
explizit zu instanziieren oder explizit zu spezialisieren
verboten
CWG 2504 C++11 das Verhalten von vererbten Konstruktoren
aus virtuellen Basisklassen war unklar
klargestellt
P0136R1 C++11 Deklaration vererbter Konstruktoren injiziert
zusätzliche Konstruktoren in die abgeleitete Klasse
bewirkt, dass Basisklassenkonstruktoren
durch Namenssuche gefunden werden

Referenzen

  • C++23-Standard (ISO/IEC 14882:2024):
  • 9.9 Die using -Deklaration [namespace.udecl]
  • C++20-Standard (ISO/IEC 14882:2020):
  • 9.9 Die using -Deklaration [namespace.udecl]
  • C++17-Standard (ISO/IEC 14882:2017):
  • 10.3.3 Die using -Deklaration [namespace.udecl]
  • C++14-Standard (ISO/IEC 14882:2014):
  • 7.3.3 Die using -Deklaration [namespace.udecl]
  • C++11-Standard (ISO/IEC 14882:2011):
  • 7.3.3 Die using -Deklaration [namespace.udecl]
  • C++03 Standard (ISO/IEC 14882:2003):
  • 7.3.3 Die using Deklaration [namespace.udecl]
  • C++98 Standard (ISO/IEC 14882:1998):
  • 7.3.3 Die using Deklaration [namespace.udecl]