Почему «используя пространство имен X;» не допускается на уровне класса / структуры?

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

Изменить : хотите узнать мотивацию.

iammilind
источник
1
@pst: в C # нет ничего подобного using namespace. C # допускает нечто подобное, но только в области файлов. C ++ using namespaceпозволяет вам включать одно пространство имен в другое.
Билли Онил,
2
Дубликат этого вопроса ?
greatwolf
@ZachSaw, я понимаю вашу озабоченность. Пробовали закрыть Qn по релевантности. Поскольку этот пост содержит более объективный ответ и ссылку на стандарт, я оставил его открытым. В прошлом многие из моих старых Qn закрывались новыми Qn ... иногда мной, иногда другими. Если вы считаете, что это решение было неуместным, отметьте его в алмазных модах. Без обид. :-)
iammilind 02
@iammilind наплевать, TBH. ТАК беспорядок в наши дни. Но пометка сообщения, которое начинается с «Я точно не знаю», как ответ, действительно содержит «более объективный ответ и ссылку на стандарт». Ха-ха.
Zach Saw
@ZachSaw, я говорил не только о принятом ответе, но и о посте в целом. Да, это объективно, но в этом ответе содержится стандартная цитата . Он начинается с «Я не знаю», потому что даже в стандарте неоправданно, почему «использование пространства имен» не разрешено внутри class/struct. Это просто не разрешено. Но в принятом ответе обсуждается очень логическое обоснование его запрета. т.е. где рассматривать Hello::Worldи где рассматривать World. Надеюсь, что это развеивает сомнения.
iammilind 02

Ответы:

36

Я не знаю точно, но предполагаю, что разрешение этого на уровне класса может вызвать путаницу:

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

Поскольку очевидного способа сделать это нет, стандарт просто говорит, что нельзя.

Теперь, когда мы говорим об областях пространства имен, это менее запутанно:

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.
    }
}
Билли Онил
источник
5
+1, я подумал об этой причине, но тогда то же самое применимо и using namespace Hello;внутри других namespace(и объявление externфункции внутри него).
iammilind
10
Я не думаю, что это сбивает с толку. C ++ - это не догадки. Если бы это было разрешено, то комитет C ++ ISO указал бы в спецификации языка. Тогда вы бы не сказали, что это сбивает с толку. В противном случае можно было бы сказать, что даже это сбивает с толку: ideone.com/npOeD ... но тогда правило для такого кодирования указано в спецификации.
Nawaz
1
@Nawaz: Большинство пользователей языка. Я никогда не говорил, что C ++ - это догадки. Я говорю, что когда спецификация разрабатывается, она разрабатывается с тем поведением, которое большинство программистов ожидают заранее. А правила на бумаге часто являются запутанным - стандартные попытки быть однозначным , но это не всегда удается.
Билли Онил,
6
В первом примере это должно быть: Hello::World Blah::DoSomething()или Blah::World Blah::DoSomething()(если это разрешено), возвращаемый тип определения функции-члена не считается находящимся в области действия класса на языке, поэтому он должен быть уточнен. Рассмотрим правильный пример замены usingс typedef Hello::World World;в области видимости класса. Так что никаких сюрпризов быть не должно.
Дэвид Родригес - дрибес,
2
Если бы это было разрешено, я считаю, что это было бы применено на уровне лексической области. Я думаю, что это «очевидное» решение, практически лишенное неожиданностей.
Thomas Eding
19

Потому что стандарт C ++ явно запрещает это. Из C ++ 03 §7.3.4 [namespace.udir]:

директива использования :
    using namespace :: opt  спецификатор вложенного имени opt  namespace-name ;

С помощью директивы не должна появляться в области видимости класса, но может появиться в области видимости пространства имен или в блоке области. [Примечание: при поиске имени пространства имен в директиве using рассматриваются только имена пространств имен, см. 3.4.6. ]

Почему это запрещено стандартом C ++? Не знаю, спросите члена комитета ISO, который утвердил языковой стандарт.

Адам Розенфилд
источник
46
Еще один технически правильный, но бесполезный ответ; худший вид. 1) больше людей, чем просто комитет, знают ответ. 2) члены комитета участвуют в SO 3) если вы не знаете ответа (учитывая дух вопроса), зачем вообще отвечать?
Catskul 03
6
@Catskul: это не бесполезный ответ. Очень полезно знать, что стандарт явно решает это и запрещает. Также парадоксально, что ответ, получивший наибольшее количество голосов, начинается с «я точно не знаю». Кроме того, «стандарт запрещает это» - это не то же самое, что «это запрещено, потому что компилятор не разрешает это», потому что в последнем случае не будут даны ответы на последующие вопросы вроде: это проблема с моим компилятором? компилятор не соответствует стандартам? это побочный эффект каких-то других вещей, о которых я не знаю? и т. д.
Антонон
9

Я считаю, что причина в том, что это, вероятно, сбивает с толку. В настоящее время при обработке идентификатора уровня класса поиск будет сначала искать в области класса, а затем во включающем пространстве имен. Разрешение using namespaceна уровне класса имело бы некоторые побочные эффекты на то, как теперь выполняется поиск. В частности, это должно было быть выполнено где-то между проверкой этой конкретной области класса и проверкой включающего пространства имен. То есть: 1) объединить уровень класса и поиски на уровне используемого пространства имен, 2) выполнить поиск используемого пространства имен после области действия класса, но перед любой другой областью класса, 3) выполнить поиск используемого пространства имен прямо перед охватывающим пространством имен. 4) поиск объединен с охватывающим пространством имен.

  1. Это сделало бы большую разницу, где идентификатор на уровне класса будет теневой любой идентификатор в вмещающем пространстве имен, но это не тень на используемое пространства имен. Эффект был бы странным, поскольку доступ к используемому пространству имен из класса в другом пространстве имен и из того же пространства имен будет отличаться:

.

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. Ищите сразу после этой области класса. Это привело бы к странному эффекту затенения членов базовых классов. Текущий поиск не смешивает поиск на уровне класса и на уровне пространства имен, и при выполнении поиска класса он будет идти до базовых классов, прежде чем рассматривать охватывающее пространство имен. Такое поведение было бы неожиданным, поскольку оно не рассматривало бы пространство имен на том же уровне, что и включающее пространство имен. Опять же, используемое пространство имен будет иметь приоритет над окружающим пространством имен.

.

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. Поиск прямо перед охватывающим пространством имен. Проблема с этим подходом опять же в том, что он многих удивит. Учтите, что пространство имен определено в другой единице трансляции, поэтому следующий код нельзя увидеть сразу:

.

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. Слияние с окружающим пространством имен. Это будет иметь тот же эффект, что и применение usingобъявления на уровне пространства имен. Это не добавит к этому никакого нового значения, но, с другой стороны, усложнит поиск разработчикам компилятора. Поиск идентификатора пространства имен теперь не зависит от того, где в коде запускается поиск. Находясь внутри класса, если поиск не находит идентификатор в области класса, он возвращается к поиску в пространстве имен, но это точно такой же поиск в пространстве имен, который используется в определении функции, нет необходимости поддерживать новое состояние. Когда usingобъявление найдено на уровне пространства имен, содержимое используемого пространства имен переносится в это пространство имен для всех поисков, связанных с этим пространством имен. Еслиusing namespace разрешено на уровне класса, будут разные результаты поиска в одном и том же пространстве имен в зависимости от того, откуда был запущен поиск, и это сделало бы реализацию поиска намного более сложной без дополнительного значения.

В любом случае я рекомендую вообще не использовать using namespaceдекларацию. Это упрощает понимание кода без необходимости помнить о содержимом всех пространств имен.

Давид Родригес - дрибеас
источник
1
Я согласен с тем, что использование имеет тенденцию создавать неявные странности. Но некоторые библиотеки могут быть разработаны с учетом того факта, что они usingсуществуют. Преднамеренно объявляя вещи в глубоко вложенных длинных пространствах имен. Например, glmделает это и использует несколько уловок для активации / представления функций при использовании клиентом using.
v.oddou
даже прямо в STL using namespace std::placeholders. cf en.cppreference.com/w/cpp/utility/functional/bind
v.oddou
@ v.oddou:namespace ph = std::placeholders;
Дэвид Родригес - dribeas
1

Вероятно, это запрещено из-за открытости и закрытости.

  • Классы и структуры в C ++ всегда являются закрытыми объектами. Они определены точно в одном месте (хотя вы можете разделить декларацию и реализацию).
  • пространства имен можно открывать, повторно открывать и расширять произвольно часто.

Импорт пространств имен в классы приведет к таким забавным случаям:

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
источник
Или мы могли бы просто НЕ определять членов класса с импортированными именами. Пусть эта конструкция добавится namespace Fooк порядку поиска для всего кода внутри определения типа struct Bar, что очень похоже на размещение этой строки в теле каждой встроенной функции-члена, за исключением того, что она также будет активна для инициализаторов скобок или равенства и т. Д. Но она все равно будет Срок действия истекает в закрывающей фигурной скобке, как и using namespaceвнутри тела функции-члена. Теперь, к сожалению, похоже, нет никакого способа использовать поиск Koenig-with-fallback в инициализаторе фигурных скобок или равных без загрязнения окружающего пространства имен.
Бен Фойгт,
0

Я считаю это дефектом языка. Вы можете использовать обходной путь ниже. Имея в виду этот обходной путь, несложно предложить правила разрешения конфликтов имен для случая, когда язык будет изменен.

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