Специализация специалиста объявления псевдонима в разных пространствах имен

Я просто столкнулся с странной разницей в поведении между clang и gcc, где я хотел скомпилировать код, который выглядит примерно так:

namespace n1 {
  template <class T1, class T2>
  struct MyTemplate {
    struct Inner {};
  };
}

using namespace n1;
namespace n2 {
  using MyClass = MyTemplate<int, int>;
}

namespace n1 {
  using n2::MyClass;
  template<> struct MyClass::Inner {
    int member;
  };

  MyClass::Inner inner{0};
}

Клан счастливо компилирует это:

$ clang++ -std=c++11 -c -o alias_specialization.o alias_specialization.cc

но gcc вызывает следующую ошибку:

$ g++ -std=c++11 -c -o alias_specialization.o alias_specialization.cc

alias_specialization:15:30: error: declaration of ‘struct n1::MyTemplate<int, int>::Inner’ in namespace ‘n1’ which does not enclose ‘using MyClass = struct n1::MyTemplate<int, int>’
   template<> struct MyClass::Inner {

Я знаю, что могу просто написать полное имя исходного типа (MyTemplate<int, int>) вместо MyClass в строке 15. Но мне просто интересно, какой из двух компиляторов "прав". Точные компиляторы используются:

$ clang++ --version
clang version 4.0.0

$ g++ --version
g++ (GCC) 6.3.1 20170306

Ответ 1

Отказ: GCC Clang прав (как указал @suluke, используя результаты ниже).

Я нашел следующие проходы в стандарте С++ 14, но я уверен, что то же самое относится к С++ 11:

Часть I: специализация и создание шаблонов

inline namespace (§7.3.1, с. 8):

Члены встроенного пространства имен могут использоваться в большинстве случаев, как если бы они были членами охватывающего пространства имен. В частности, встроенное пространство имен и его охватывающее пространство имен добавляются к набору связанных пространств имен, используемых в зависимости от зависимостей поиска (3.4.2), когда это необходимо, и директивой using (7.3.4), которая называет встроенное пространство имен неявно вставляется в охватываемое пространство имен как для неназванного пространства имен (7.3.1.1). Кроме того, каждый член встроенного пространства имен может впоследствии быть частично специализированным (14.5.5), явно созданным (14.7.2) или явно специализированным (14.7.3), как если бы он был членом охватывающее пространство имен. Наконец, поиск имени в охватывающем пространстве имен с помощью явной квалификации (3.4.3.2) будет включать в себя членов встроенного пространства имен, внесенного с помощью директивы using, даже если в этом пространстве имен есть объявления этого имени.

Явное копирование (§ 14.7.2, гл. 3)

Если явное инстанцирование относится к классу или классу-члену, специфицированный спецификатор типа в объявлении должен содержать идентификатор simple-template-id. Если явное инстанцирование используется для функции или функции-члена, в объявлении неквалифицированный идентификатор должен быть либо идентификатором-шаблоном, либо, где могут быть выведены все аргументы шаблона, имя шаблона или идентификатор-функция-идентификатор. [Примечание. Объявление может объявлять идентификатор с квалификацией, и в этом случае идентификатор с неквалифицированным идентификатором должен быть идентификатором шаблона. -end note] Если явное инстанцирование для функции-члена, класса-члена или статического члена данных специализации шаблона класса, имя специализации шаблона класса в идентификаторе-квалифицированном имени для имени участника должно быть простым шаблоном -Я бы. Если явное инстанцирование относится к переменной, в объявлении неквалифицированный идентификатор должен быть идентификатором шаблона. Явное инстанцирование должно появиться во вмещающем пространстве имен его шаблона. Если имя, объявленное в явном экземпляре, является неквалифицированным именем, явное инстанцирование должно появиться в пространстве имен, где объявлен его шаблон, или, если это пространство имен является встроенным (7.3.1), любое пространство имен из его охватывающего пространства имен установлено.

Явная реализация (§ 14.7.2, гл. 6)

Явное создание экземпляра класса, шаблона функции или специализации по шаблону помещается в пространство имен, в котором определяется шаблон. Явное инстанцирование для члена шаблона класса помещается в пространство имен, где определяется шаблон вмещающего класса. Явное инстанцирование для шаблона-члена помещается в пространство имен, в котором задан закрытый класс или шаблон класса. [Пример:

namespace N {
    template<class T> class Y { void mf() { } };
  }
  template class Y<int>;  // error: class template Y not visible 
                          // in the global namespace

  using N::Y;
  template class Y<int>; // error: explicit instantiation outside of the 
                         // namespace of the template


  template class N::Y<char*>;  // OK: explicit instantiation in namespace N
  template void N::Y<double>::mf();  // OK: explicit instantiation
                                     // in namespace N

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

Часть II: Typedef, сглаживание

Что такое typedef или псевдоним?

§7.1.3 cl. 1:

[...] Имя, объявленное с помощью спецификатора typedef, становится typedef-name. В пределах своей декларации имя typedef синтаксически эквивалентно ключевому слову и называет тип, связанный с идентификатором, способом, описанным в разделе 8. Таким образом, typedef-name является синонимом другого типа. Имя typedef не вводит новый тип, как это делает объявление класса (9.1) или объявление enum.

§7.1.3 cl. 2 состояния:

Имя typedef также может быть введено alias-declaration. Идентификатор, следующий за ключевым словом using, становится typedef-name и необязательным атрибутом-спецификатором-seq, следующим за идентификатором, к этому typedef-name. Он имеет ту же семантику, как если бы он был введен спецификатором typedef. В частности, он не определяет новый тип, и он не должен отображаться в идентификаторе типа.

Часть III: Проверка вашего случая

Следуя вашей логике, следующий код должен скомпилировать и создать 2 разных типа. Я тестировал его с помощью Clang, который является компилятором. Clang производит тот же тип, что и стандарт, который он требует.

#include <iostream>

namespace n1 {
  template <class T1, class T2>
  struct MyTemplate {
    struct Inner {};
  };
}

using namespace n1;
namespace n2 {
  using MyClass = MyTemplate<int, int>;
}

namespace n3 {
  using MyClass = MyTemplate<int, int>;
}

namespace n1 {
  using n2::MyClass;
  template<> struct MyClass::Inner {
    int member;
  };

  MyClass::Inner inner{0};
}

namespace n4{
  using n3::MyClass;

  MyClass::Inner inner{0};
}

int main()
{
  using namespace std;

  cout << typeid(n1::inner).name() << endl;
  cout << typeid(n4::inner).name() << endl;

  return 0;
}

Компиляция

c++ -std=c++14 typedef-typeid.cpp -O3 -o compiled.bin

ИЛИ

c++ -std=c++11 typedef-typeid.cpp -O3 -o compiled.bin

Вывод в обоих случаях

N2n110MyTemplateIiiE5InnerE
N2n110MyTemplateIiiE5InnerE

Как оказалось, в этом контексте typedef и using эквивалентны, мы можем использовать typedef вместо using.

§7.3.3 cl. 1 указывает на это:

[...] Если декларация using называет конструктор (3.4.3.1), он неявно объявляет набор конструкторов в классе, в котором появляется декларация использования (12.9); иначе имя, указанное в объявлении using, является синонимом набора объявлений в другом пространстве имен или классе.

#include <iostream>
#include <typeinfo>

namespace n1 {
  template <class T1, class T2>
  struct MyTemplate {
    struct Inner {};
  };
}

using namespace n1;
namespace n2 {
  typedef MyTemplate<int, int> MyClass;
}


namespace n1 {
  typedef n2::MyClass MyClass;

  template<> struct MyClass::Inner {
    int member;
  };

  MyClass::Inner inner{0};
}

int main()
{
  using namespace std;

  cout << typeid(n1::inner).name() << endl;

  return 0;
}

И GCC отлично компилирует пример, не обвиняя ничего.

Кроме того, было бы интересно узнать, как транзитивность с объявлением using работает с GCC.

Вот пример:

#include <iostream>
#include <typeinfo>

namespace n1 {
  template <class T1, class T2>
  struct MyTemplate {
    struct Inner {};
  };
}


using namespace n1;
namespace n2 {
  using MyClass = MyTemplate<int, int>;

}

namespace n3
{
  using n2::MyClass;
}


namespace n1 {
  typedef n3::MyClass MyClass;

  template<> struct MyClass::Inner {
    int member;
  };

  MyClass::Inner inner{0};
}

int main()
{
  using namespace std;

  cout << typeid(n1::inner).name() << endl;

  return 0;
}

И снова и Clang, и GCC счастливо скомпилируют его.