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

Introduit un nom qui est défini ailleurs dans la région déclarative où cette déclaration using apparaît. Voir using enum et (depuis C++20) using namespace pour les autres déclarations apparentées.

using typename (optionnel) spécificateur-de-nom-imbriqué identifiant-non-qualifié ; (jusqu'en C++17)
using liste-déclarateurs ; (depuis C++17)
typename - le mot-clé typename peut être utilisé si nécessaire pour résoudre les noms dépendants , lorsque la déclaration using introduit un type membre d'une classe de base dans un modèle de classe
nested-name-specifier - une séquence de noms et d'opérateurs de résolution de portée :: , se terminant par un opérateur de résolution de portée. Un simple :: fait référence à l'espace de noms global.
unqualified-id - une expression d'identifiant
declarator-list - liste séparée par des virgules d'un ou plusieurs déclarateurs du typename (optionnel) nested-name-specifier unqualified-id . Certains ou tous les déclarateurs peuvent être suivis d'une ellipse ... pour indiquer une expansion de paquet

Table des matières

Explication

Les using-declarations peuvent être utilisées pour introduire des membres d'un espace de noms dans d'autres espaces de noms et portées de bloc, ou pour introduire des membres de classe de base dans les définitions de classes dérivées , ou pour introduire des enumerators dans les espaces de noms, portées de bloc et portées de classe (depuis C++20) .

Une déclaration using avec plusieurs using-déclarateurs est équivalente à une séquence correspondante de déclarations using avec un seul using-déclarateur.

(depuis C++17)

Dans l'espace de noms et la portée de bloc

Using-declarations introduisent un membre d'un autre espace de noms dans l'espace de noms courant ou la portée de bloc.

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

Voir namespace pour plus de détails.

Dans la définition de classe

La déclaration using introduit un membre d'une classe de base dans la définition de la classe dérivée, par exemple pour exposer un membre protégé de la base comme membre public de la dérivée. Dans ce cas, nested-name-specifier doit nommer une classe de base de celle en cours de définition. Si le nom est celui d'une fonction membre surchargée de la classe de base, toutes les fonctions membres de la classe de base portant ce nom sont introduites. Si la classe dérivée possède déjà un membre avec le même nom, la même liste de paramètres et les mêmes qualifications, le membre de la classe dérivée masque ou remplace (ne rentre pas en conflit avec) le membre introduit depuis la classe de base.

#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 est protégé
    typedef int value_type;
};
struct D : B
{
    using B::m;          // D::m est public
    using B::value_type; // D::value_type est public
    using B::f;
    void f(int) override { std::cout << "D::f\n"; } // D::f(int) remplace B::f(int)
    using B::g;
    void g(int) { std::cout << "D::g\n"; } // g(int) et g(char) sont tous deux visibles
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) masque B::h(int)
};
int main()
{
    D d;
    B& b = d;
//  b.m = 2;  // Erreur : B::m est protégé
    d.m = 1;  // B::m protégé est accessible en tant que D::m public
    b.f(1);   // appelle f() dérivé
    d.f(1);   // appelle f() dérivé
    std::cout << "----------\n";
    d.g(1);   // appelle g(int) dérivé
    d.g('a'); // appelle g(char) de base, exposé via using B::g;
    std::cout << "----------\n";
    b.h(1);   // appelle h() de base
    d.h(1);   // appelle h() dérivé
}

Sortie :

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

Héritage des constructeurs

Si la using-declaration fait référence à un constructeur d'une base directe de la classe en cours de définition (par exemple using Base :: Base ; ), tous les constructeurs de cette base (en ignorant le contrôle d'accès) sont rendus visibles pour la résolution de surcharge lors de l'initialisation de la classe dérivée.

Si la résolution de surcharge sélectionne un constructeur hérité, il est accessible s'il serait accessible lors de la construction d'un objet de la classe de base correspondante : l'accessibilité de la déclaration using qui l'a introduit est ignorée.

Si la résolution de surcharge sélectionne l'un des constructeurs hérités lors de l'initialisation d'un objet d'une telle classe dérivée, alors le Base sous-objet duquel le constructeur a été hérité est initialisé en utilisant le constructeur hérité, et toutes les autres bases et membres de Derived sont initialisés comme par le constructeur par défaut implicitement déclaré (les initialiseurs de membres par défaut sont utilisés s'ils sont fournis, sinon l'initialisation par défaut a lieu). L'initialisation entière est traitée comme un seul appel de fonction : l'initialisation des paramètres du constructeur hérité est séquencée avant l'initialisation de toute base ou membre de l'objet dérivé.

struct B1 { B1(int, ...) {} };
struct B2 { B2(double)   {} };
int get();
struct D1 : B1
{
    using B1::B1; // hérite de B1(int, ...)
    int x;
    int y = get();
};
void test()
{
    D1 d(2, 3, 4); // OK : B1 est initialisé en appelant B1(2, 3, 4),
                   // puis d.x est initialisé par défaut (aucune initialisation n'est effectuée),
                   // puis d.y est initialisé en appelant get()
    D1 e;          // Erreur : D1 n'a pas de constructeur par défaut
}
struct D2 : B2
{
    using B2::B2; // hérite de B2(double)
    B1 b;
};
D2 f(1.0); // erreur : B1 n'a pas de constructeur par défaut
struct W { W(int); };
struct X : virtual W
{
    using W::W; // hérite de W(int)
    X() = delete;
};
struct Y : X
{
    using X::X;
};
struct Z : Y, virtual W
{
    using Y::Y;
};
Z z(0); // OK : l'initialisation de Y n'invoque pas le constructeur par défaut de X

Si le sous-objet de la classe de base Base ne doit pas être initialisé dans le cadre de l'objet Derived (c'est-à-dire si Base est une classe de base virtuelle de Derived , et que l'objet Derived n'est pas l' objet le plus dérivé ), l'appel du constructeur hérité, y compris l'évaluation de tout argument, est omis :

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” n'est pas invoqué, car le sous-objet V
         // n'est pas initialisé en tant que partie de B
         // (le sous-objet V est initialisé en tant que partie de C,
         //  car “c” est l'objet le plus dérivé)
}

Si le constructeur était hérité de plusieurs sous-objets de classe de base de type Base , le programme est mal formé, similaire aux fonctions membres non statiques héritées multiplement :

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); // mal formé : constructeur hérité de différents sous-objets de base B
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 : il n'y a qu'un seul sous-objet B.
          // Ceci initialise la classe de base virtuelle B,
          //   qui initialise la classe de base A
          // puis initialise les classes de base V1 et V2
          //   comme par un constructeur par défaut généré automatiquement

Comme pour les déclarations using de toute autre fonction membre non statique, si un constructeur hérité correspond à la signature de l'un des constructeurs de Derived , il est masqué lors de la recherche par la version trouvée dans Derived . Si l'un des constructeurs hérités de Base se trouve avoir la signature qui correspond à un constructeur de copie/déplacement de Derived , cela n'empêche pas la génération implicite du constructeur de copie/déplacement de Derived (qui masque alors la version héritée, similairement à 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) masque à la fois B1::B1(int) et B2::B2(int)
};
D2 d2(0);    // appelle D2::D2(int)

Dans une classe template , si une déclaration using fait référence à un nom dépendant , il est considéré comme nommant un constructeur si le nested-name-specifier a un nom terminal identique au unqualified-id .

template<class T>
struct A : T
{
    using T::T; // OK, hérite des constructeurs de T
};
template<class T, class U>
struct B : T, A<U>
{
    using A<U>::A; // OK, hérite des constructeurs de A<U>
    using T::A;    // n'hérite pas du constructeur de T
                   // même si T peut être une spécialisation de A<>
};
(depuis C++11)


Introduction des énumérateurs délimités

En plus des membres d'un autre espace de noms et des membres des classes de base, la déclaration using peut également introduire les énumérateurs des énumérations dans les portées de namespace, de bloc et de classe.

Une déclaration using peut également être utilisée avec des énumérateurs non délimités.

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
(depuis C++20)

Notes

Seul le nom explicitement mentionné dans la déclaration using est transféré dans la portée déclarative : en particulier, les énumérateurs ne sont pas transférés lorsque le nom du type énumération est déclaré via using.

Une déclaration using ne peut pas faire référence à un espace de noms , à un énumérateur scopé (jusqu'au C++20) , à un destructeur d'une classe de base ou à une spécialisation d'un modèle de membre pour une fonction de conversion définie par l'utilisateur.

Une déclaration using ne peut pas nommer une spécialisation de modèle de membre ( template-id n'est pas autorisé par la grammaire) :

struct B
{
    template<class T>
    void f();
};
struct D : B
{
    using B::f;      // OK : nomme un template
//  using B::f<int>; // Erreur : nomme une spécialisation de template
    void g() { f<int>(); }
};

Une déclaration using ne peut pas non plus être utilisée pour introduire le nom d'un modèle de membre dépendant en tant que template-name (le désambiguïsateur template pour les noms dépendants n'est pas autorisé).

template<class X>
struct B
{
    template<class T>
    void f(T);
};
template<class Y>
struct D : B<Y>
{
//  using B<Y>::template f; // Erreur : désambiguïsateur non autorisé
    using B<Y>::f;          // compile, mais f n'est pas un nom de template
    void g()
    {
//      f<int>(0);          // Erreur : f n'est pas reconnu comme nom de template,
                            // donc < ne démarre pas une liste d'arguments de template
        f(0);               // OK
    }   
};

Si une déclaration using introduit l'opérateur d'assignation de la classe de base dans la classe dérivée, dont la signature correspond par hasard à l'opérateur d'assignation de copie ou de déplacement de la classe dérivée, cet opérateur est masqué par l'opérateur d'assignation de copie/déplacement implicitement déclaré de la classe dérivée. La même règle s'applique à une déclaration using qui hérite d'un constructeur de classe de base qui correspond par hasard au constructeur de copie/déplacement de la classe dérivée (depuis C++11) .

La sémantique des constructeurs hérités a été modifiée rétroactivement par un rapport de défaut contre C++11 . Auparavant, une déclaration de constructeur hérité provoquait l'injection d'un ensemble de déclarations de constructeurs synthétisés dans la classe dérivée, ce qui entraînait des copies/déplacements redondants d'arguments, avait des interactions problématiques avec certaines formes de SFINAE, et dans certains cas pouvait être impossible à implémenter sur les ABI majeurs. Les compilateurs plus anciens peuvent encore implémenter l'ancienne sémantique.

Ancienne sémantique des constructeurs hérités

Si la using-declaration fait référence à un constructeur d'une base directe de la classe en cours de définition (par exemple using Base :: Base ; ), les constructeurs de cette classe de base sont hérités, selon les règles suivantes :

1) Un ensemble de constructeurs hérités candidats est composé de
a) Tous les constructeurs non templates de la classe de base (après omission des paramètres ellipses, s'il y en a) (depuis C++14)
b) Pour chaque constructeur avec des arguments par défaut ou le paramètre ellipsis, toutes les signatures de constructeur formées en supprimant l'ellipsis et en omettant les arguments par défaut depuis la fin des listes d'arguments un par un
c) Tous les templates de constructeur de la classe de base (après omission des paramètres ellipses, s'il y en a) (depuis C++14)
d) Pour chaque template de constructeur avec des arguments par défaut ou l'ellipsis, toutes les signatures de constructeur formées en supprimant l'ellipsis et en omettant les arguments par défaut depuis la fin des listes d'arguments un par un
2) Tous les constructeurs hérités candidats qui ne sont pas le constructeur par défaut ou le constructeur de copie/déplacement et dont les signatures ne correspondent pas aux constructeurs définis par l'utilisateur dans la classe dérivée, sont implicitement déclarés dans la classe dérivée. Les paramètres par défaut ne sont pas hérités :
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
};

Les constructeurs hérités sont équivalents à des constructeurs définis par l'utilisateur avec un corps vide et avec une liste d'initialisation des membres constituée d'un seul nested-name-specifier , qui transmet tous ses arguments au constructeur de la classe de base.

Il a le même accès que le constructeur de base correspondant. Il est constexpr si le constructeur défini par l'utilisateur aurait satisfait aux exigences du constructeur constexpr . Il est supprimé si le constructeur de base correspondant est supprimé ou si un constructeur par défaut par défaut serait supprimé (sauf que la construction de la base dont le constructeur est hérité ne compte pas). Un constructeur hérité ne peut pas être explicitement instancié ou explicitement spécialisé.

Si deux using-declarations héritent du constructeur avec la même signature (depuis deux classes de base directes), le programme est mal formé.

Un template de constructeur hérité ne doit pas être explicitement instancié ou explicitement spécialisé .

(depuis C++11)

Les expansions de paquets dans les déclarations using permettent de former une classe qui expose les membres surchargés de bases variadiques sans récursion :

template<typename... Ts>
struct Overloader : Ts...
{
    using Ts::operator()...; // expose operator() de chaque base
};
template<typename... T>
Overloader(T...) -> Overloader<T...>; // guide de déduction C++17, non requis en C++20
int main()
{
    auto o = Overloader{ [] (auto const& a) {std::cout << a;},
                         [] (float f) {std::cout << std::setprecision(3) << f;} };
}
(depuis C++17)
Macro de test de fonctionnalité Valeur Std Fonctionnalité
__cpp_inheriting_constructors 200802L (C++11) Constructeurs hérités
201511L (C++17)
(DR11)
Reformulation des constructeurs hérités
__cpp_variadic_using 201611L (C++17) Développements de paquets dans les déclarations using

Mots-clés

using

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement publié Comportement corrigé
CWG 258 C++98 une fonction membre non-const d'une classe dérivée peut
redéfinir et/ou masquer une fonction membre const de sa base
la redéfinition et le masquage nécessitent aussi
que les qualifications cv soient identiques
CWG 1738 C++11 il n'était pas clair s'il était permis d'instancier
explicitement ou de spécialiser explicitement
les spécialisations des modèles de constructeurs hérités
interdit
CWG 2504 C++11 le comportement de l'héritage des constructeurs
depuis les classes de base virtuelles était peu clair
clarifié
P0136R1 C++11 la déclaration de constructeur hérité injecte
des constructeurs supplémentaires dans la classe dérivée
permet aux constructeurs de la classe de base
d'être trouvés par la recherche de nom

Références

  • Norme C++23 (ISO/IEC 14882:2024) :
  • 9.9 La déclaration using [namespace.udecl]
  • Norme C++20 (ISO/IEC 14882:2020) :
  • 9.9 La déclaration using [namespace.udecl]
  • Norme C++17 (ISO/CEI 14882:2017) :
  • 10.3.3 La déclaration using [namespace.udecl]
  • Norme C++14 (ISO/CEI 14882:2014) :
  • 7.3.3 La déclaration using [namespace.udecl]
  • Norme C++11 (ISO/CEI 14882:2011) :
  • 7.3.3 La déclaration using [namespace.udecl]
  • Norme C++03 (ISO/CEI 14882:2003) :
  • 7.3.3 La déclaration using [namespace.udecl]
  • Norme C++98 (ISO/CEI 14882:1998) :
  • 7.3.3 La déclaration using [namespace.udecl]