Using-declaration
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 constructeursSi 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
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
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
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
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ésEn 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.
|
(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
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]
-
9.9 La déclaration
- Norme C++20 (ISO/IEC 14882:2020) :
-
-
9.9 La déclaration
using[namespace.udecl]
-
9.9 La déclaration
- Norme C++17 (ISO/CEI 14882:2017) :
-
-
10.3.3 La déclaration
using[namespace.udecl]
-
10.3.3 La déclaration
- Norme C++14 (ISO/CEI 14882:2014) :
-
-
7.3.3 La déclaration
using[namespace.udecl]
-
7.3.3 La déclaration
- Norme C++11 (ISO/CEI 14882:2011) :
-
-
7.3.3 La déclaration
using[namespace.udecl]
-
7.3.3 La déclaration
- Norme C++03 (ISO/CEI 14882:2003) :
-
-
7.3.3 La déclaration
using[namespace.udecl]
-
7.3.3 La déclaration
- Norme C++98 (ISO/CEI 14882:1998) :
-
-
7.3.3 La déclaration
using[namespace.udecl]
-
7.3.3 La déclaration