Как работает поиск неквалифицированного имени при использовании объявлений использования?

Это неправильно или правильно сформировано в соответствии со стандартом С++?

namespace M { struct i {}; }
namespace N { static int i = 1; }
using M::i;
using N::i;
int main() { sizeof (i); }

Clang отклоняет его, а GCC принимает.

Согласно [namespace.udir-6] (http://eel.is/c++draft/basic.namespace#namespace.udir-6):

Если поиск имени находит объявление имени в двух разных пространствах имен, и объявления не объявляют одну и ту же сущность и не объявляют функции, использование имени является неверным.

Как мы должны интерпретировать это? Помните, что каждая декларация использования объявляет имя через [namespace.udecl]p1 (http://eel.is/c++draft/namespace.udecl#1):

Объявление-использования вводит имя в декларативную область, в которой появляется объявление-использования.

объявление-использования:
using typenameopt описатель вложенного-имени неполный-id ;

Имя члена, указанное в объявлении-использования, объявляется в области деклараций, в которой появляется объявление-использования. [Примечание: объявляется только указанное имя; указание имени перечисления в объявлении использования не объявляет его перечислители в декларативной области объявления использования. — конец примечания ] Если объявление-использования называет конструктор ([class.qual]), оно неявно объявляет набор конструкторов в классе, в котором появляется объявление-использования ([class.inhctor]); в противном случае имя, указанное в декларации использования, является синонимом набора объявлений в другом пространстве имен или классе.

Итак, у нас есть 4 объявления имени i.

Что из этого находит неполный поиск имени i в sizeof(i)?

Находит ли он только using M::i; и using N::i;, которые находятся в одном и том же пространстве имен (глобальном пространстве имен), поэтому программа имеет правильный формат?

Или он находит только struct i {}; и static int i = 1;, которые находятся в разных пространствах имен, поэтому программа имеет неправильный формат?

Или у нас есть другая альтернатива?


person Supremum    schedule 29.07.2015    source источник
comment
Я уверен, что недавно видел вопрос об этом, который пришел к выводу, что это ошибка. Вы искали?   -  person Lightness Races in Orbit    schedule 30.07.2015
comment
Вопрос был не совсем тот. Это своего рода уточняющий вопрос.   -  person Supremum    schedule 30.07.2015
comment
@LightnessRacesinOrbit О, хорошо. Я не думал об этом. Спасибо, что указали на это.   -  person jaggedSpire    schedule 30.07.2015


Ответы (2)


У bogdan уже есть ответ, но, чтобы понять, почему ваша интуиция неверна, вы процитировали:

Если поиск имени находит объявление для имени в двух разных пространствах имен, и объявления не объявляют одну и ту же сущность и не объявляют функции, использование имени является неправильным.

Но в примере имеем:

namespace M { 
    struct i {};           // declares M::i, entity class type
}
namespace N { 
    static int i = 1;      // declares N::i, entity variable
}
using M::i;                // declares ::i, synonym of M::i
using N::i;                // declares ::i, synonym of N::i
                           // hides (*) the other ::i
int main() { 
    sizeof (i); 
}

Чтобы уточнить (*), у нас есть два объявления i в глобальном пространстве имен ::. Из [basic.scope.hiding]:

Имя класса (9.1) или имя перечисления (7.2) может быть скрыто именем переменной, элемента данных, функции или перечислителя, объявленных в той же области. Если имя класса или перечисления и переменная, член данных, функция или перечислитель объявлены в одной и той же области (в любом порядке) с одним и тем же именем, имя класса или перечисления скрыто везде, где переменная, член данных, функция или имя перечислителя видно.

Таким образом, с двумя i в одной области видимости класс скрыт (независимо от порядка объявлений-использования!), а sizeof(i) относится к ::i, который является синоним N::i. Оба i находились в одном пространстве имен (::), поэтому ваша цитата неприменима. Это отличается от вашего более раннего вопроса, где вместо этого у вас были using-directives:

using namespace M;
using namespace N;

Там i будет находиться в двух разных пространствах имен, ссылаясь на два разных нефункциональных объекта. Отсюда и ошибка. Здесь Clang ошибается, а GCC прав.

person Barry    schedule 29.07.2015

N4527 [7.3.3p13]:

Поскольку объявление-использования является объявлением, ограничения на объявления с тем же именем в той же области объявлений (3.3) также применяются к объявлениям-использования. [ Пример:

namespace A {
   int x;
}

namespace B {
   int i;
   struct g { };
   struct x { };
   void f(int);
   void f(double);
   void g(char);    // OK: hides struct g
}

void func() {
   int i;
   using B::i;      // error: i declared twice
   void f(char);
   using B::f;      // OK: each f is a function
   f(3.5);          // calls B::f(double)
   using B::g;
   g(’a’);          // calls B::g(char)
   struct g g1;     // g1 has class type B::g
   using B::x;
   using A::x;      // OK: hides struct B::x
   x = 99;          // assigns to A::x
   struct x x1;     // x1 has class type B::x
}

—конец примера ]

Обратите внимание на объявления использования для двух разных x — это тот же случай, что и в вашем примере.


Ваша первая цитата относится к директивам использования, а не к объявлениям использования.

Поиск неполного имени для i в sizeof(i) находит i в глобальном пространстве имен. Поскольку они являются объявлениями в одной области видимости, согласно [3.3.10p2] (цитата ниже), переменная i скрывает переменную struct.

Имя класса (9.1) или имя перечисления (7.2) может быть скрыто именем переменной, элемента данных, функции или перечислителя, объявленных в той же области. Если имя класса или перечисления и переменная, член данных, функция или перечислитель объявлены в одной и той же области (в любом порядке) с одним и тем же именем, имя класса или перечисления скрыто везде, где переменная, член данных, функция или имя перечислителя видно.

Итак, код составлен правильно, и Clang ошибается, отказываясь от него.

MSVC (12 и 14) принимает пример.


По сути, думайте об имени, представленном декларацией-использования, как о просто другом имени для некоторой сущности, которая также названа где-то еще (место, указанное вложенным-спецификатором-имени полный-id в декларации-использования). Это отличается от того, что делает using-directive; Я склонен думать об using-directive как о «настройках поиска имен».

person bogdan    schedule 29.07.2015
comment
Близость терминов using-declaration и using-directive не помогает понять, я уверен... :) - person Barry; 30.07.2015
comment
@ Барри Совершенно верно. И в классическом стиле C++ оба они делают более одной вещи в зависимости от контекста. Не говоря уже о самом ключевом слове using, которое еще более перегружено. - person bogdan; 30.07.2015