朋友模板参数相关查找

friend template argument-dependent lookup

已知在 class 范围内定义的友元函数可以通过参数相关查找找到,所以我们必须在友元函数类型中使用 class 类型,但是如果我们定义友元函数在 class 之外,它的函数参数可以留空。 那么这对模板朋友是如何工作的,如果我们在 class 之外进行专门化,它应该像在 class 范围之外定义的普通朋友函数一样可见。

#include <iostream>    
class A
{
public:
        A()
         : x(20)
        {}
        template <typename T>
        friend void foo()
        {
               std::cout << "Primary Template" << std::endl;
        }
        friend void goo();
private:
        int x;
};

void goo() 
{
    std::cout << "some goo" << std::endl;
}


template <>
void foo<int>()
{
        std::cout << "specialization" << std::endl;
}

int main()
{
        A a;
        foo<int>(); //  VS 2012 gives error C3767: 'foo': candidate function(s) 
                    // not accessible
                    //  'foo'  [may be found via argument-dependent lookup]
        goo();      // OK
}

那么,为什么 goo 是可见和可访问的,而 foo 对 int 的专门化却不是? VisualStudio 2012 给出错误“'foo':候选函数不可访问,'foo' [可以通过参数相关查找找到]”。顺便说一句,GCC 编译代码没有错误。标准中有任何限制还是这只是编译器问题?

我认为 Clang 在这里可能是正确的。考虑两件事:

§14.7.3/8 [temp.expl.spec],稍微删减示例:

A template explicit specialization is in the scope of the namespace in which the template was defined. [Example:

namespace N {
    template<class T> class X { /* ... */ };
    template<> class X<int> { /* ... */ }; // OK: specialization 
                                           // in same namespace
}
template <> class N::X<double> { /* ... */ }; // OK: specialization
                                              // in enclosing namespace

—end example ]

但是,根据 §11.3/7 [class.friend](强调我的),所讨论的友元函数 foo 位于:

A friend function defined in a class is in the (lexical) scope of the class in which it is defined.

要提供 foo 的专业化,它必须在 A 的词法范围内——我认为这是不可能的。您的 foo<int> 专业化范围错误。

请注意,这仅适用于 class 中定义的函数。以下代码在 Clang 上为我编译和运行很好,因为现在 goo 不在 M 的范围内:

#include <iostream>
struct M
{
    template <typename T>  
    friend void goo();     // only declared, not defined
};

template <typename T>
void goo() { 
    std::cout << "full" << std::endl;
}

template <>
void goo<int>() {
    std::cout << "explicit" << std::endl;
}

int main()
{
    goo<int>(); // prints explicit
}

可见性规则在 §7.3.1.2/3 中建立(强调我的):

The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1) or qualified lookup (3.4.3). [ Note: The name of the friend will be visible in its namespace if a matching declaration is provided at namespace scope (either before or after the class definition granting friendship). —end note ]

因此,在这个更简单的示例中:

struct M {
    template <typename T> friend void foo(T ) { }
};

foo 是在 M 中定义的,因此它位于 M 的词法范围内。在 M 之外没有 foo 的 "matching declaration",所以它应该只在 ADL 中可见(§3.4.2/4,强调我的):

When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2) except that

(4.1) - Any using-directives in the associated namespace are ignored.

(4.2) - Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3).

int main() {
    foo(M{}); // compiles correctly on both GCC and Clang
    foo(0);   // should fail. Clang complains about "use of undeclared identifier 'foo'"
              // but GCC 4.9.2 allows it! (update: fixed in 5+)
}

所以我重申我的第一句话:我认为 Clang 在这里可能是正确的。