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?
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With