2015-05-23 2 views
13

Следующий код, который не компилируется под звоном, но делает при GCC и VS:clang bug? пространство имен класса шаблон друг

template<typename T> class bar; 

namespace NS 
{ 
    template<typename T> 
    class foo 
    { 
     foo() {} 

     template<typename U> friend class bar; 
    }; 
} 

template<typename R> 
class bar 
{ 
public: 
    bar() 
    { 
     NS::foo<int> f; 
    } 
}; 


int main(int, char **) 
{ 
    bar<int> b;   
    return 0; 
} 

Он терпит неудачу с:

main.cpp:20:22: error: calling a private constructor of class 'NS::foo<int>' 

     NS::foo<int> f;  
        ^

main.cpp:8:9: note: implicitly declared private here 

     foo() {} 
     ^

bar должны иметь доступ к foo-х частный конструктор, но похоже, что нет. Если я удалю namespace NS, он скомпилируется.

Код выглядит хорошо для меня, но, возможно, я не понимаю стандарт C++. Какой компилятор прав?

+1

Замечание: если вы квалифицируете ':: bar', как' template класс friend :: bar; ', то clang компилирует его. Таким образом, похоже, что-то связано с видимостью друзей вне пространства имен. Глядя на какой-то вопрос SO, похоже, что clang ++ верен, хотя я еще не нашел обмана. – vsoftco

ответ

14

Я считаю, что clang является правильным. Согласно [namespace.memdef]/3:

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

В вашем случае имя не будет объявлено «первым объявленным» в объявлении friend. Позже в этом пункте, однако, внимание мое:

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

То есть, эта декларация:

template<typename U> friend class bar; 

не будет искать bar снаружи namespace NS, поэтому он не будет найти свой ранее заявление. Таким образом, он объявляет шаблон класса NS::bar<typename > равным friend из foo. Вы должны право и название bar для того, чтобы найти:

template<typename U> friend class ::bar; 

Это кажется связано с GCC Bug 37804.

2

Изменение кода

template<typename T> class bar; 

namespace NS 
{ 
    template<typename T> 
    class foo 
    { 
     foo() {} 

     template<typename U> friend class ::bar; 
    }; 
} 

template<typename R> 
class bar 
{ 
public: 
    bar() 
    { 
     NS::foo<int> f; 
    } 
}; 


int main(int, char **) 
{ 
    bar<int> b; 

    return 0; 
} 

компилирует с лязгом. Проблема заключается в поиске пространства имен. Код

template<typename U> friend class bar; 

фактически объявлен класс NS :: запретить друга NS :: Foo, так Foo не были затронуты. Я предполагаю, что clang стандартно соответствует, и код не должен компилироваться.

+0

OP хочет знать: «Кто прав?» –

5

От cppreference:

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

void h(int); 
namespace A { 
    class X { 
    friend void f(X); // A::f is a friend 
    class Y { 
     friend void g(); // A::g is a friend 
     friend void h(int); // A::h is a friend, no conflict with ::h 
    }; 
    }; 
    // A::f, A::g and A::h are not visible at namespace scope 
    // even though they are members of the namespace A 
    X x; 
    void g() { // definition of A::g 
    f(x); // A::X::f is found through ADL 
    } 
    void f(X) {}  // definition of A::f 
    void h(int) {}  // definition of A::h 
    // A::f, A::g and A::h are now visible at namespace scope 
    // and they are also friends of A::X and A::X::Y 
} 

Это не стандарт, но это в целом правильно. Таким образом, clang, кажется, прав.

Смежные вопросы