Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

clang bug? namespaced template class' friend

The following code which doesn't compile under clang but does under gcc and VS:

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

It fails with:

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 should have access to foo's private constructor but it looks like it doesn't. If I remove namespace NS, it compiles.

Code looks fine to me, but maybe I'm misunderstanding the C++ standard. Which compiler is correct?

like image 645
Michał Walenciak Avatar asked May 23 '15 22:05

Michał Walenciak


3 Answers

I believe that clang is correct. According to [namespace.memdef]/3:

Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class, function, class template or function template the friend is a member of the innermost enclosing namespace.

In your case, the name wouldn't appear to be "first declared" by the friend declaration. Later in that paragraph, however, emphasis mine:

If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.

That is, this declaration:

template<typename U> friend class bar;

will not look for bar outside of namespace NS, so it will not find your earlier declaration. As such, it declares a class template NS::bar<typename > to be a friend of foo. You will have to qualify the name bar in order for it to be found:

template<typename U> friend class ::bar;

This seems related to GCC Bug 37804.

like image 191
Barry Avatar answered Nov 20 '22 04:11

Barry


From cppreference:

Names introduced by friend declarations within a non-local class X become members of the innermost enclosing namespace of X, but they do not become visible to lookup (neither unqualified nor qualified) unless a matching declaration is provided at namespace scope, either before or after the class definition. Such name may be found through ADL which considers both namespaces and classes. Only the innermost enclosing namespace is considered by such friend declaration when deciding whether the name would conflict with a previously declared name.

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
}

It's not the standard, but it is in general correct. So clang seems to be right.

like image 28
vsoftco Avatar answered Nov 20 '22 06:11

vsoftco


Changing the code to

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

compiles with clang. The problem seems to be the namespace lookup. The code

template<typename U> friend class bar;

actually declared the class NS::bar a friend of NS::foo, so foo should not have been affected. My guess would be that clang is standard conformant and the code should not compile.

like image 2
RedAgito Avatar answered Nov 20 '22 05:11

RedAgito