Por qué "usar el espacio de nombres X" no está permitido dentro del nivel de clase / estructura?

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

Editar : quiero saber la motivación detrás de esto.

iammilind
fuente
1
@pst: C # no tiene nada como using namespace. C # permite algo similar, pero solo en el ámbito del archivo. C ++ le using namespacepermite incorporar un espacio de nombres en otro.
Billy ONeal
2
¿Duplicado de esta pregunta ?
greatwolf
@ZachSaw, entiendo tu preocupación. He intentado cerrar el Qn en función de la relevancia. Dado que esta publicación contiene una respuesta y una referencia más objetivas al estándar, la he mantenido abierta. En el pasado, muchos de mis Qn más antiguos fueron cerrados por Qn más nuevos ... a veces por mí, a veces por otros. Por favor marque los Mods de diamantes, si cree que esta decisión no fue apropiada. Sin resentimientos. :-)
iammilind
@iammilind no podría importarle menos TBH. Así que es un desastre estos días. Pero marcar una publicación que comienza con "No sé exactamente" como respuesta realmente contiene "una respuesta más objetiva y una referencia al estándar". Jaja.
Zach Saw
@ZachSaw, no solo estaba hablando de la respuesta aceptada, sino de la publicación general. Sí, es objetivo, pero la cita estándar está contenida en esta respuesta . Comienza con "No lo sé", porque incluso en el estándar, no se justifica por qué no se permite "usar espacio de nombres" en el interior class/struct. Simplemente no está permitido. Pero la respuesta aceptada discute un razonamiento muy lógico para rechazarlo. es decir, dónde considerar Hello::Worldy dónde considerar World. Espero que despeje la duda.
iammilind

Respuestas:

36

No lo sé exactamente, pero supongo que permitir esto en el ámbito de la clase podría causar confusión:

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?
}

Dado que no hay una forma obvia de hacer esto, el estándar simplemente dice que no se puede.

Ahora, la razón por la que esto es menos confuso cuando hablamos de ámbitos de espacio de nombres:

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
fuente
5
+1, pensé en esta razón, pero lo mismo es aplicable para using namespace Hello;dentro de otro namespacetambién (y declarando la externfunción dentro de él).
iammilind
10
No creo que sea confuso. C ++ no se trata de conjeturas. Si estuviera permitido, entonces el comité ISO de C ++ lo habría especificado en la especificación del lenguaje. Entonces no dirías que es confuso. De lo contrario, se podría decir que incluso esto es confuso: ideone.com/npOeD ... pero luego la regla para tal codificación se especifica en la especificación.
Nawaz
1
@Nawaz: la mayoría de los usuarios del idioma. Nunca dije que C ++ se tratara de conjeturas. Estoy diciendo que cuando se diseña la especificación, se diseña con el comportamiento que la mayoría de programadores esperan de antemano. Y las reglas en papel a menudo son confusas: el estándar intenta ser inequívoco, pero no siempre tiene éxito.
Billy ONeal
6
En el primer ejemplo, debería ser: Hello::World Blah::DoSomething()o Blah::World Blah::DoSomething()(si se permitió), el tipo de retorno de una definición de función miembro no se considera que esté en el alcance de la clase en el lenguaje, por lo que debe estar calificado. Considere el ejemplo válido de reemplazar el alcance de la clase por usingun typedef Hello::World World;at. Así que no debería haber sorpresas allí.
David Rodríguez - dribeas
2
Si se permitiera, creo que se aplicaría a nivel de alcance léxico. Creo que esta es la solución "obvia" prácticamente sin sorpresas.
Thomas Eding
19

Porque el estándar C ++ lo prohíbe explícitamente. De C ++ 03 §7.3.4 [namespace.udir]:

directiva-de-uso :
    usando namespace :: opt  nested-name-specifier opt  namespace-name ;

Una directiva de uso no aparecerá en el ámbito de la clase, pero puede aparecer en el ámbito del espacio de nombres o en el ámbito del bloque. [Nota: al buscar un nombre de espacio de nombres en una directiva using, solo se consideran los nombres de espacios de nombres, consulte 3.4.6. ]

¿Por qué lo prohíbe el estándar C ++? No sé, pregúntele a un miembro del comité ISO que aprobó el estándar de lenguaje.

Adam Rosenfield
fuente
46
Otra respuesta técnicamente correcta pero inútil; el peor tipo. 1) más personas que solo el comité conocen la respuesta. 2) los miembros del comité participan en el OE 3) si no sabe la respuesta (dado el espíritu de la pregunta) ¿por qué responder?
Catskul
6
@Catskul: no es una respuesta inútil. Es muy útil saber que el estándar lo aborda explícitamente y lo prohíbe. También es irónico que la respuesta más votada comience con "No sé exactamente". Además, el "estándar lo prohíbe" no es lo mismo que "no está permitido porque el compilador no lo permite", porque el último caso no respondería preguntas de seguimiento como: ¿es un problema con mi compilador? ¿El compilador no cumple con los estándares? ¿Es un efecto secundario de algunas otras cosas que no conozco? etc.
Antonone
9

Creo que la razón es que probablemente sería confuso. Actualmente, mientras se procesa un identificador de nivel de clase, la búsqueda buscará primero en el ámbito de la clase y luego en el espacio de nombres adjunto. Permitir el using namespacenivel a nivel de clase tendría algunos efectos secundarios sobre cómo se realiza ahora la búsqueda. En particular, tendría que realizarse en algún momento entre la verificación de ese ámbito de clase particular y la verificación del espacio de nombres adjunto. Es decir: 1) fusionar el nivel de clase y las búsquedas de nivel de espacio de nombres usado, 2) buscar el espacio de nombre usado después del alcance de clase pero antes de cualquier otro alcance de clase, 3) buscar el espacio de nombre usado justo antes del espacio de nombre adjunto. 4) la búsqueda se fusionó con el espacio de nombres adjunto.

  1. Esto haría una gran diferencia, donde un identificador a nivel de clase sombrearía cualquier identificador en el espacio de nombres adjunto, pero no sombrearía un espacio de nombres usado . El efecto sería extraño, ya que el acceso al espacio de nombres usado desde una clase en un espacio de nombres diferente y desde el mismo espacio de nombres sería diferente:

.

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. Busque justo después de este ámbito de clase. Esto tendría el extraño efecto de sombrear a los miembros de las clases base. La búsqueda actual no mezcla búsquedas a nivel de clase y de espacio de nombres, y al realizar la búsqueda de clases, irá hasta las clases base antes de considerar el espacio de nombres adjunto. El comportamiento sería sorprendente porque no consideraría el espacio de nombres en un nivel similar al espacio de nombres adjunto. Nuevamente, el espacio de nombres usado se priorizaría sobre el espacio de nombres adjunto.

.

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. Busque justo antes del espacio de nombres adjunto. El problema con este enfoque es, nuevamente, que sería sorprendente para muchos. Tenga en cuenta que el espacio de nombres está definido en una unidad de traducción diferente, por lo que el siguiente código no se puede ver todo a la vez:

.

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. Fusionar con el espacio de nombres adjunto. Esto tendría exactamente el mismo efecto que aplicar la usingdeclaración en el nivel del espacio de nombres. No agregaría ningún valor nuevo a eso, pero por otro lado complicará la búsqueda de los implementadores del compilador. La búsqueda de identificadores de espacio de nombres ahora es independiente de en qué parte del código se desencadena la búsqueda. Cuando dentro de una clase, si la búsqueda no encuentra el identificador en el ámbito de la clase, recurrirá a la búsqueda de espacio de nombres, pero esa es exactamente la misma búsqueda de espacio de nombres que se usa en una definición de función, no es necesario mantener un nuevo estado. Cuando la usingdeclaración se encuentra a nivel de espacio de nombres, el contenido del espacio de nombres usado se lleva a ese espacio de nombres para todas las búsquedas que involucran el espacio de nombres. Siusing namespace se permitió a nivel de clase, habría diferentes resultados para la búsqueda de espacio de nombres del mismo espacio de nombres exacto dependiendo de dónde se desencadenó la búsqueda, y eso haría que la implementación de la búsqueda sea mucho más compleja sin valor adicional.

De todos modos, mi recomendación es no emplear la using namespacedeclaración en absoluto. Hace que el código sea más fácil de razonar sin tener que tener en cuenta el contenido de todos los espacios de nombres.

David Rodríguez - dribeas
fuente
1
Estoy de acuerdo en que el uso tiende a crear rarezas implícitas. Pero algunas bibliotecas pueden diseñarse teniendo en cuenta el hecho de que usingexiste. Declarando cosas a propósito en espacios de nombres largos anidados profundos. Por ejemplo, glmhace eso y usa múltiples trucos para activar / presentar características cuando el cliente usa using.
v.oddou
incluso en el 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

Esto probablemente no esté permitido debido a la apertura frente a la cercanía.

  • Las clases y estructuras en C ++ son siempre entidades cerradas. Se definen exactamente en un lugar (aunque puede dividir la declaración y la implementación).
  • los espacios de nombres se pueden abrir, volver a abrir y ampliar arbitrariamente a menudo.

La importación de espacios de nombres en clases conduciría a casos divertidos como este:

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
fuente
O simplemente NO podríamos definir miembros de clase con los nombres importados. Deje que esta construcción se agregue namespace Fooal orden de búsqueda para todo el código dentro de la definición de tipo de struct Bar, muy parecido a poner esa línea en cada cuerpo de función miembro en línea, excepto que también estaría activa para inicializadores de llaves o iguales, etc. expiran en la llave de cierre, lo mismo que using namespacedentro de un cuerpo de función miembro. Ahora, desafortunadamente, no parece haber ninguna forma de usar la búsqueda Koenig-with-fallback en un inicializador de llaves o iguales sin contaminar el espacio de nombres adjunto.
Ben Voigt
0

Creo que es un defecto del idioma. Puede utilizar la solución alternativa a continuación. Teniendo en cuenta esta solución alternativa, es fácil sugerir reglas de resolución de conflictos de nombres para el caso en que se cambie el idioma.

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
fuente
¿Puede agregar alguna explicación?
Kishan Bharda
Sí, he agregado algunos comentarios
naprimeroleg