Por que “usando o namespace X;” não é permitido dentro do nível de classe / estrutura?

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

Edit : Quer saber a motivação por trás disso.

iammilind
fonte
1
@pst: C # não tem nada parecido using namespace. C # permite algo semelhante, mas apenas no escopo do arquivo. O C ++ using namespacepermite que você incorpore um namespace a outro.
Billy ONeal
2
Duplicado desta pergunta ?
greatwolf de
@ZachSaw, entendo sua preocupação. Já tentei fechar o Qn com base na relevância. Como esta postagem contém respostas mais objetivas e referências ao padrão, mantive-a aberta. No passado, muitos dos meus Qn mais antigos eram fechados pelo Qn mais recente .. às vezes por mim às vezes por outros. Por favor, sinalize para os Mods diamante, caso sinta que esta decisão não foi apropriada. Sem ressentimentos. :-)
iammilind
@iammilind não está nem aí, TBH. ASSIM está uma bagunça nos dias de hoje. Mas marcar uma postagem que começa com "Não sei exatamente" como resposta realmente contém "resposta mais objetiva e referência ao padrão". Haha.
Zach viu
@ZachSaw, eu não estava falando apenas sobre a resposta aceita, mas sobre a postagem geral. Sim, é objetivo, mas a citação padrão está contida nesta resposta . Começa com "Não sei", porque mesmo no padrão, não se justifica porque "usar namespace" não é permitido dentro class/struct. Simplesmente não é permitido. Mas a resposta aceita discute uma justificativa muito lógica para rejeitá-la. ou seja, onde considerar Hello::Worlde onde considerar World. Espero que isso esclareça a dúvida.
iammilind

Respostas:

36

Não sei exatamente, mas meu palpite é que permitir isso no escopo da classe pode causar confusão:

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

Como não há uma maneira óbvia de fazer isso, o padrão apenas diz que você não pode.

Agora, o motivo disso é menos confuso quando estamos falando de escopos de namespace:

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
fonte
5
+1, pensei neste motivo, mas então a mesma coisa se aplica para using namespace Hello;dentro de outro namespacetambém (e declarar externfunção dentro dele).
iammilind
10
Eu não acho que seja confuso. C ++ não é uma questão de suposições. Se fosse permitido, o comitê C ++ ISO teria especificado na especificação do idioma. Então você não diria que é confuso. Caso contrário, pode-se dizer que até isso é confuso: ideone.com/npOeD ... mas a regra para tal codificação é especificada na especificação.
Nawaz
1
@Nawaz: A maioria dos usuários do idioma. Eu nunca disse que C ++ era uma questão de adivinhação. Estou dizendo que, quando a especificação é projetada, ela é projetada com o comportamento que a maioria dos programadores espera com antecedência. E as regras no papel costumam ser confusas - o padrão tenta ser inequívoco, mas nem sempre tem sucesso.
Billy ONeal
6
No primeiro exemplo, deve ser: Hello::World Blah::DoSomething()ou Blah::World Blah::DoSomething()(se permitido), o tipo de retorno de uma definição de função de membro não é considerado no escopo da classe na linguagem, portanto, deve ser qualificado. Considere o exemplo válido de substituição de usingpor um typedef Hello::World World;no escopo da classe. Portanto, não deve haver surpresas aí.
David Rodríguez - dribeas
2
Se fosse permitido, acredito que seria aplicado em um nível de escopo léxico. Acho que essa é a solução "óbvia", praticamente sem surpresas.
Thomas Eding
19

Porque o padrão C ++ proíbe explicitamente isso. De C ++ 03 §7.3.4 [namespace.udir]:

using-Directive :
    using namespace :: opt  nested-name-especificador opt  namespace-name ;

Uma diretiva de uso não deve aparecer no escopo da classe, mas pode aparecer no escopo do namespace ou no escopo do bloco. [Nota: ao pesquisar um nome de espaço de nomes em uma diretiva de uso, apenas nomes de espaços de nomes são considerados, consulte 3.4.6. ]

Por que o padrão C ++ o proíbe? Não sei, pergunte a um membro do comitê ISO que aprovou o padrão de idioma.

Adam Rosenfield
fonte
48
Mais uma resposta tecnicamente correta, mas inútil; o pior tipo. 1) mais pessoas do que apenas o comitê sabem a resposta. 2) membros do comitê participam do SO 3) se você não sabe a resposta (dado o espírito da pergunta), por que responder?
Catskul
7
@Catskul: não é uma resposta inútil. É muito útil saber que o padrão aborda isso explicitamente e o proíbe. Também é irônico que a resposta mais votada comece com "Não sei exatamente". Além disso, o "padrão proíbe" não é o mesmo que "não é permitido porque o compilador não permite", porque o último caso não responderia a perguntas de acompanhamento como: é um problema com meu compilador? o compilador não é compatível com o padrão? é um efeito colateral de algumas outras coisas das quais não estou ciente? etc.
antonone de
9

Eu acredito que o raciocínio é que provavelmente seria confuso. Atualmente, ao processar um identificador de nível de classe, a pesquisa pesquisará primeiro no escopo da classe e, em seguida, no namespace delimitador. Permitir o using namespaceem nível de classe teria alguns efeitos colaterais sobre como a pesquisa agora é realizada. Em particular, ele teria que ser executado em algum momento entre a verificação do escopo da classe em particular e a verificação do namespace envolvente. Isto é: 1) mesclar o nível de classe e as pesquisas de nível de namespace usado, 2) pesquisar o namespace usado após o escopo da classe, mas antes de qualquer outro escopo de classe, 3) pesquisar o namespace usado antes do namespace delimitador. 4) lookup mesclado com o namespace delimitador.

  1. Isso faria uma grande diferença, onde um identificador no nível de classe iria sombrear qualquer identificador no namespace delimitador, mas não iria sombrear um namespace usado . O efeito seria estranho, pois o acesso ao namespace usado de uma classe em um namespace diferente e do mesmo namespace seria 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. Procure logo após este escopo de classe. Isso teria o estranho efeito de obscurecer os membros das classes base. A pesquisa atual não mistura pesquisas de nível de classe e de namespace e, ao realizar a pesquisa de classe, ela percorrerá todo o caminho até as classes básicas antes de considerar o namespace delimitador. O comportamento seria surpreendente, pois não consideraria o namespace em um nível semelhante ao namespace delimitador. Novamente, o namespace usado teria prioridade sobre o namespace delimitador.

.

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. Pesquise imediatamente antes do namespace delimitador. O problema com essa abordagem é, novamente, que seria surpreendente para muitos. Considere que o namespace é definido em uma unidade de tradução diferente, de modo que o código a seguir não pode ser visto de uma 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. Mesclar com o namespace delimitador. Isso teria exatamente o mesmo efeito que aplicar a usingdeclaração no nível do namespace. Isso não adicionaria nenhum valor novo a isso, mas, por outro lado, complicaria a pesquisa de implementadores de compilador. A pesquisa do identificador de namespace agora é independente de onde no código a pesquisa é acionada. Quando dentro de uma classe, se a pesquisa não encontrar o identificador no escopo da classe, ela retornará à pesquisa de espaço de nomes, mas essa é exatamente a mesma pesquisa de espaço de nomes usada em uma definição de função, não há necessidade de manter o novo estado. Quando a usingdeclaração é encontrada no nível do namespace, o conteúdo do namespace usado é trazido para esse namespace para todas as pesquisas envolvendo o namespace. E seusing namespace fosse permitido no nível de classe, haveria resultados diferentes para a pesquisa de namespace do mesmo namespace, dependendo de onde a pesquisa foi disparada, e isso tornaria a implementação da pesquisa muito mais complexa sem nenhum valor adicional.

De qualquer forma, minha recomendação é não usar a using namespacedeclaração de forma alguma. Ele torna o código mais simples de raciocinar, sem ter que manter todos os conteúdos dos namespaces em mente.

David Rodríguez - dribeas
fonte
1
Concordo que o uso tende a criar estranhezas implícitas. Mas algumas bibliotecas podem ser projetadas em torno do fato de que usingexiste. Declarando coisas propositalmente em namespaces longos aninhados. Por exemplo, glmfaz isso e usa vários truques para ativar / apresentar recursos quando o cliente usa using.
v.oddou
mesmo certo no 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

Isso provavelmente não é permitido por causa da abertura versus fechamento.

  • Classes e estruturas em C ++ são sempre entidades fechadas. Eles são definidos em exatamente um lugar (embora você possa dividir a declaração e a implementação).
  • namespaces podem ser abertos, reabertos e estendidos arbitrariamente com freqüência.

Importar namespaces para classes levaria a casos engraçados 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
fonte
Ou simplesmente NÃO podemos definir os membros da classe com os nomes importados. Deixe esta construção adicionar namespace Fooà ordem de pesquisa para todo o código dentro da definição de tipo de struct Bar, de forma muito semelhante a colocar essa linha em cada corpo de função de membro embutido, exceto que também estaria ativo para inicializadores de chave ou igual, etc. Mas ainda estaria expira na chave de fechamento, o mesmo que using namespacedentro do corpo de uma função de membro. Agora, infelizmente, não parece haver nenhuma maneira de usar a pesquisa Koenig-com-fallback em um inicializador de chave ou igual sem poluir o namespace delimitador.
Ben Voigt
0

Acho que é um defeito da linguagem. Você pode usar a solução alternativa abaixo. Tendo em mente essa solução alternativa, é fácil sugerir regras de resolução de conflitos de nomes para o caso em que o idioma será alterado.

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
fonte
Você pode adicionar alguma explicação?
Kishan Bharda
Sim, adicionei alguns comentários
naprimeroleg