铿锵错误?命名空间模板 class' 朋友

clang bug? namespaced template class' friend

以下代码不能在 clang 下编译,但可以在 gcc 和 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;
}

它失败了:

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++ 标准。哪个编译器是正确的?

将代码更改为

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

用 clang 编译。问题似乎是名称空间查找。 代码

template<typename U> friend class bar;

实际上声明 class NS::bar 是 NS::foo 的朋友,所以 foo 不应该受到影响。我的猜测是 clang 是符合标准的,代码不应该编译。

我相信clang是正确的。根据 [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.

在您的例子中,friend 声明的名称似乎不是 "first declared"。然而,在该段的后面,强调我的:

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.

也就是这个声明:

template<typename U> friend class bar;

不会在 namespace NS 之外寻找 bar,因此它不会找到您之前的声明。因此,它将 class 模板 NS::bar<typename > 声明为 foofriend。您必须 限定 名称 bar 才能找到它:

template<typename U> friend class ::bar;

这似乎与 GCC Bug 37804 有关。

来自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
}

这不是标准,但大体上是正确的。所以clang似乎是对的。