Pourquoi «utiliser l'espace de noms X»; n'est pas autorisé au niveau de la classe / structure?

89
class C {
  using namespace std;  // error
};
namespace N {
  using namespace std; // ok
}
int main () {
  using namespace std; // ok
}

Edit : Vous voulez connaître la motivation derrière cela.

iammilind
la source
1
@pst: C # n'a rien de tel using namespace. C # permet quelque chose de similaire, mais à la portée du fichier uniquement. C ++ using namespacevous permet d'incorporer un espace de noms dans un autre.
Billy ONeal
2
Duplicata de cette question ?
greatwolf
@ZachSaw, je comprends votre inquiétude. J'ai essayé de fermer le Qn en fonction de la pertinence. Puisque cet article contient une réponse plus objective et une référence à la norme, je l'ai gardé ouvert. Dans le passé, beaucoup de mes anciens Qn ont été fermés par les nouveaux Qn .. parfois par moi parfois par d'autres. Veuillez signaler les Mods diamantés, si vous estimez que cette décision n'est pas appropriée. Pas d'émotions fortes. :-)
iammilind
@iammilind s'en fiche de TBH. SO est un gâchis ces jours-ci. Mais marquer un message qui commence par "Je ne sais pas exactement" comme réponse contient vraiment "une réponse plus objective et une référence à la norme". Haha.
Zach Saw
@ZachSaw, je ne parlais pas seulement de la réponse acceptée, mais de l'ensemble du message. Oui c'est objectif mais le devis standard est contenu dans cette réponse . Cela commence par "Je ne sais pas", car même en standard, il n'est pas justifié de ne pas autoriser "utiliser l'espace de noms" à l'intérieur class/struct. Ce n'est tout simplement pas autorisé. Mais la réponse acceptée discute une justification très logique pour la rejeter. c'est-à-dire où considérer Hello::Worldet où considérer World. J'espère que cela dissipe le doute.
iammilind

Réponses:

36

Je ne sais pas exactement, mais je suppose que permettre cela à la portée de la classe pourrait causer de la confusion:

namespace Hello
{
    typedef int World;
}

class Blah
{
    using namespace Hello;
public:
    World DoSomething();
}

//Should this be just World or Hello::World ?
World Blah::DoSomething()
{
    //Is the using namespace valid in here?
}

Comme il n'y a pas de moyen évident de le faire, la norme dit simplement que vous ne pouvez pas.

Maintenant, la raison pour laquelle cela est moins déroutant lorsque nous parlons d'étendues d'espace de noms:

namespace Hello
{
    typedef int World;
}

namespace Other
{
    using namespace Hello;
    World DoSomething();
}

//We are outside of any namespace, so we have to fully qualify everything. Therefore either of these are correct:

//Hello was imported into Other, so everything that was in Hello is also in Other. Therefore this is okay:
Other::World Other::DoSomething()
{
    //We're outside of a namespace; obviously the using namespace doesn't apply here.
    //EDIT: Apparently I was wrong about that... see comments. 
}

//The original type was Hello::World, so this is okay too.
Hello::World Other::DoSomething()
{
    //Ditto
}

namespace Other
{
    //namespace Hello has been imported into Other, and we are inside Other, so therefore we never need to qualify anything from Hello.
    //Therefore this is unambiguiously right
    World DoSomething()
    {
        //We're inside the namespace, obviously the using namespace does apply here.
    }
}
Billy ONeal
la source
5
+1, j'ai pensé à cette raison, mais la même chose est applicable à l' using namespace Hello;intérieur des autres namespaceégalement (et en déclarant la externfonction à l'intérieur).
iammilind
10
Je ne pense pas que ce soit déroutant. C ++ n'est pas une question de conjectures. Si cela avait été autorisé, alors le comité ISO C ++ aurait spécifié dans la spécification du langage. Alors vous ne diriez pas que c'est déroutant. Sinon, on pourrait dire que même cela prête à confusion: ideone.com/npOeD ... mais alors la règle pour un tel codage est spécifiée dans la spécification.
Nawaz
1
@Nawaz: la plupart des utilisateurs de la langue. Je n'ai jamais dit que C ++ était une question de conjectures. Je dis que lorsque la spécification est conçue, elle est conçue avec le comportement auquel la plupart des programmeurs s'attendront à l'avance. Et les règles sur papier sont souvent déroutantes - la norme tente d'être sans ambiguïté, mais elle ne réussit pas toujours.
Billy ONeal
6
Sur le premier exemple, cela devrait être: Hello::World Blah::DoSomething()ou Blah::World Blah::DoSomething()(si cela était autorisé), le type de retour d'une définition de fonction membre n'est pas considéré comme faisant partie de la portée de la classe dans le langage, il doit donc être qualifié. Prenons l'exemple valide de remplacement de la usingpar une typedef Hello::World World;portée de classe at. Il ne devrait donc y avoir aucune surprise.
David Rodríguez - dribeas
2
Si elle était autorisée, je pense qu'elle s'appliquerait au niveau de la portée lexicale. Je pense que c'est la solution «évidente» sans pratiquement aucune surprise.
Thomas Eding
19

Parce que le standard C ++ l'interdit explicitement. De C ++ 03 §7.3.4 [namespace.udir]:

using-directive :
    using namespace :: opt  imbriqué-name-spécificateur opt  namespace-name ;

Une directive using ne doit pas apparaître dans la portée de la classe, mais peut apparaître dans la portée de l'espace de noms ou dans la portée du bloc. [Note: lors de la recherche d'un nom d'espace de noms dans une directive using, seuls les noms d'espace de noms sont pris en compte, voir 3.4.6. ]

Pourquoi le standard C ++ l'interdit? Je ne sais pas, demandez à un membre du comité ISO qui a approuvé la norme linguistique.

Adam Rosenfield
la source
46
Encore une autre réponse techniquement correcte mais inutile; le pire. 1) plus de personnes que le comité connaissent la réponse. 2) les membres du comité participent à l'OS 3) si vous ne connaissez pas la réponse (étant donné l'esprit de la question) pourquoi y répondre?
Catskul
6
@Catskul: ce n'est pas une réponse inutile. Il est très utile de savoir que la norme aborde explicitement cela et l'interdit. Il est également ironique que la réponse la plus positive commence par «Je ne sais pas exactement». De plus, le "standard l'interdit" n'est pas la même chose que "il n'est pas autorisé parce que le compilateur ne le permet pas", car ce dernier cas ne répondrait pas aux questions de suivi comme: est-ce un problème avec mon compilateur? le compilateur n'est-il pas conforme aux normes? est-ce un effet secondaire d'autres choses dont je ne suis pas au courant? etc.
antonone
9

Je pense que le raisonnement est que ce serait probablement déroutant. Actuellement, lors du traitement d'un identificateur de niveau de classe, la recherche recherche d'abord dans la portée de la classe, puis dans l'espace de noms englobant. Autoriser le using namespaceniveau de la classe aurait des effets secondaires sur la façon dont la recherche est maintenant effectuée. En particulier, il devrait être effectué entre la vérification de cette portée de classe particulière et la vérification de l'espace de noms englobant. C'est-à-dire: 1) fusionner le niveau de classe et les recherches au niveau de l'espace de noms utilisé, 2) rechercher l'espace de noms utilisé après la portée de classe mais avant toute autre portée de classe, 3) rechercher l'espace de noms utilisé juste avant l'espace de noms englobant. 4) recherche fusionnée avec l'espace de noms englobant.

  1. Cela ferait une grande différence, où un identifiant au niveau de la classe serait l' ombre un identifiant dans l'espace qui entoure, mais il ne serait pas l' ombre d' un employé espace de noms. L'effet serait étrange, dans la mesure où l'accès à l' espace de noms utilisé à partir d'une classe dans un espace de noms différent et à partir du même espace de noms serait différent:

.

namespace A {
   void foo() {}
   struct B {
      struct foo {};
      void f() {
         foo();      // value initialize a A::B::foo object (current behavior)
      }
   };
}
struct C {
   using namespace A;
   struct foo {};
   void f() {
      foo();         // call A::foo
   }
};
  1. Recherchez juste après cette portée de classe. Cela aurait pour effet étrange d'observer les membres des classes de base. La recherche actuelle ne mélange pas les recherches au niveau de la classe et de l'espace de noms, et lors de la recherche de classe, elle ira jusqu'aux classes de base avant de considérer l'espace de noms englobant. Le comportement serait surprenant en ce qu'il ne considérerait pas l'espace de noms à un niveau similaire à l'espace de noms englobant. Encore une fois, l' espace de noms utilisé serait prioritaire sur l'espace de noms englobant.

.

namespace A {
   void foo() {}
}
void bar() {}
struct base {
   void foo();
   void bar();
};
struct test : base {
   using namespace A;
   void f() {
      foo();           // A::foo()
      bar();           // base::bar()
   }
};
  1. Recherche juste avant l'espace de noms englobant. Le problème avec cette approche est encore une fois qu'elle serait surprenante pour beaucoup. Considérez que l'espace de noms est défini dans une unité de traduction différente, de sorte que le code suivant ne peut pas être vu en même temps:

.

namespace A {
   void foo( int ) { std::cout << "int"; }
}
void foo( double ) { std::cout << "double"; }
struct test {
   using namespace A;
   void f() {
      foo( 5.0 );          // would print "int" if A is checked *before* the
                           // enclosing namespace
   }
};
  1. Fusionner avec l'espace de noms englobant. Cela aurait exactement le même effet que l'application de la usingdéclaration au niveau de l'espace de noms. Cela n'ajouterait aucune nouvelle valeur à cela, mais compliquerait en revanche la recherche des implémenteurs de compilateurs. La recherche d'identifiant d'espace de noms est désormais indépendante de l'endroit où la recherche est déclenchée dans le code. À l'intérieur d'une classe, si la recherche ne trouve pas l'identificateur dans la portée de la classe, elle reviendra à la recherche d'espace de noms, mais c'est exactement la même recherche d'espace de noms que celle utilisée dans une définition de fonction, il n'est pas nécessaire de maintenir un nouvel état. Lorsque la usingdéclaration est trouvée au niveau de l'espace de noms, le contenu de l' espace de noms utilisé est introduit dans cet espace de noms pour toutes les recherches impliquant l'espace de noms. Siusing namespace était autorisé au niveau de la classe, il y aurait des résultats différents pour la recherche d'espace de noms du même espace de noms exactement selon l'endroit d'où la recherche a été déclenchée, et cela rendrait l'implémentation de la recherche beaucoup plus complexe sans valeur supplémentaire.

Quoi qu'il en soit, ma recommandation est de ne pas utiliser du using namespacetout la déclaration. Cela rend le code plus simple à raisonner sans avoir à garder à l'esprit le contenu de tous les espaces de noms.

David Rodríguez - Dribeas
la source
1
Je reconnais que l'utilisation tend à créer des bizarreries implicites. Mais certaines bibliothèques peuvent être conçues autour du fait qui usingexiste. En déclarant délibérément des choses dans de longs espaces de noms imbriqués. Par exemple, glmfait cela et utilise plusieurs astuces pour activer / présenter des fonctionnalités lorsque le client utilise using.
v.oddou le
même en plein dans la STL using namespace std::placeholders. cf en.cppreference.com/w/cpp/utility/functional/bind
v.oddou
@ v.oddou:namespace ph = std::placeholders;
David Rodríguez - dribeas
1

Ceci est probablement interdit en raison de l' ouverture par rapport à la fermeture.

  • Les classes et les structures en C ++ sont toujours des entités fermées. Ils sont définis à un seul endroit (bien que vous puissiez séparer la déclaration et l'implémentation).
  • les espaces de noms peuvent être ouverts, rouverts et étendus arbitrairement souvent.

L'importation d'espaces de noms dans des classes conduirait à des cas amusants comme celui-ci:

namespace Foo {}

struct Bar { using namespace Foo; };

namespace Foo {
using Baz = int; // I've just extended `Bar` with a type alias!
void baz(); // I've just extended `Bar` with what looks like a static function!
// etc.
}
DanielS
la source
Ou nous pourrions simplement PAS définir les membres de la classe avec les noms importés. Laissez cette construction ajouter namespace Fooà l'ordre de recherche pour tout le code à l'intérieur de la définition de type de struct Bar, un peu comme mettre cette ligne dans chaque corps de fonction membre en ligne, sauf qu'elle serait également active pour les initialiseurs d'accolade ou d'égalité, etc. expire à l'accolade fermante, comme à l' using namespaceintérieur d'un corps de fonction membre. Maintenant, il ne semble malheureusement pas y avoir de moyen d'utiliser la recherche Koenig-with-fallback dans un initialiseur d'accolade ou d'égalité sans polluer l'espace de noms englobant.
Ben Voigt
0

Je pense que c'est un défaut de langage. Vous pouvez utiliser la solution de contournement ci-dessous. En gardant à l'esprit cette solution de contournement, il est facile de suggérer des règles de résolution des conflits de noms pour le cas où la langue sera modifiée.

namespace Hello
{
    typedef int World;
}
// surround the class (where we want to use namespace Hello)
// by auxiliary namespace (but don't use anonymous namespaces in h-files)
namespace Blah_namesp {
using namespace Hello;

class Blah
{
public:
    World DoSomething1();
    World DoSomething2();
    World DoSomething3();
};

World Blah::DoSomething1()
{
}

} // namespace Blah_namesp

// "extract" class from auxiliary namespace
using Blah_namesp::Blah;

Hello::World Blah::DoSomething2()
{
}
auto Blah::DoSomething3() -> World
{
}
naprimeroleg
la source
Pouvez-vous s'il vous plaît ajouter quelques explications?
Kishan Bharda
Oui, j'ai ajouté quelques commentaires
naprimeroleg