2015-05-23 4 views
13

Poniższy kod, który nie skompilować pod brzękiem ale robi pod gcc i VS:clang bug? przestrzeni nazw klasy szablon przyjaciel

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

nie jest on z:

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 powinni mieć dostęp do foo „s prywatny konstruktor, ale wygląda na to, że tak nie jest. Jeśli usuniemy namespace NS, kompiluje.

Kod wygląda dobrze, ale może nie rozumiem standardu C++. Który kompilator jest poprawny?

+1

Uwaga boczna: jeśli zakwalifikujesz ':: bar', np.' Szablon friend class :: bar; ', wtedy clang skompiluje go. Wygląda więc na coś związanego z widocznością znajomych poza obszarem nazw. Patrząc na niektóre pytania typu SO, wydaje się, że klajster ++ jest poprawny, chociaż nie znalazłem (jeszcze) duplikatu. – vsoftco

Odpowiedz

14

Wierzę, że klang jest poprawny. Według [namespace.memdef]/3:

Każda nazwa zadeklarowana jako pierwsza w przestrzeni nazw jest członkiem tej przestrzeni nazw. Jeśli deklaracja friend w klasie nielokalnej najpierw deklaruje klasę, funkcję, szablon klasy lub szablon funkcji, przyjaciel jest członkiem najgłębiej otaczającej przestrzeni nazw.

W twoim przypadku nazwa nie wydaje się być "pierwsza deklarowana" przez deklarację friend. Później w tym ustępie, jednak mój nacisk:

Jeśli nazwa w deklaracji friend jest ani kwalifikacje ani szablon id i deklaracja jest funkcją lub opracowana-type-specifier, sprawdzanie w celu ustalenia, czy jednostka ma została wcześniej zadeklarowana jako , nie uwzględnia żadnych zakresów poza najbardziej wewnętrzną przestrzenią nazw.

Oznacza to, że ta deklaracja:

template<typename U> friend class bar; 

nie będzie szukał bar zewnątrz namespace NS, więc nie znajdzie swoją wcześniejszą deklarację. W związku z tym deklaruje szablon klasy NS::bar<typename >, aby być friend z z 0123. Trzeba będzie zakwalifikować nazwą bar, aby mogła ona zostać znaleziona:

template<typename U> friend class ::bar; 

To wydaje związane GCC Bug 37804.

2

Zmiana kodu do

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

kompiluje z brzękiem. Problemem wydaje się być wyszukiwanie przestrzeni nazw. Kod

template<typename U> friend class bar; 

faktycznie ogłosił NS klasy :: Bar a przyjaciel NS :: foo foo więc nie powinno być naruszone. Domyślam się, że klang jest standardową zgodną i kod nie powinien się kompilować.

+0

PO chce wiedzieć "Kto ma rację?" –

5

Od cppreference:

Nazwy wprowadzone deklaracji przyjaciel w nielokalnego klasy X stać się członkami najbardziej wewnętrznej otaczającej przestrzeni nazw X, ale nie widoczne do wyszukiwania (ani bez zastrzeżeń ani kwalifikacje) , chyba że w obszarze przestrzeni nazw zostanie dostarczona zgodna deklaracja: przed lub po definicji klasy. Tę nazwę można znaleźć za pomocą ADL , która uwzględnia zarówno przestrzenie nazw, jak i klasy. Tylko najbardziej wewnętrzna przestrzeń nazwana jest uważana przez taką deklarację przyjaciela, gdy określa ona, czy nazwa będzie kolidować z wcześniej zadeklarowaną nazwą: .

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 
} 

To nie jest standard, ale jest ogólnie poprawne. Więc klang wydaje się mieć rację.