3

It'n known that friend function defined in class scope can be found via argument-dependent lookup so we have to use class type in friend function type, but if we define friend function outside of class it function parameters can be left empty. So how this works for template friends, if we have specialization outside of class it should be visible as ordinary friend function defined outside of class scope.

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

So why goo is visible and accessible but foo's specialization for int not? VisualStudio 2012 gives error "'foo': candidate function(s) not accessible, 'foo' [may be found via argument-dependent lookup]". By the way GCC compiles code without errors. Is there any restrictions in standard or this is just compilers issues?

  • Working fine with gcc 4.9.2 http://ideone.com/iYvJFM – Ankur Jan 13 '15 at 08:31
  • Yes I mentioned that GCC compiles code without errors. – Vahagn Babajanyan Jan 13 '15 at 09:25
  • 1
    Interesting. clang 3.5 pukes on this. Adding `template void foo();` prior to the specialization fixes it for clang, btw. It seems the decl in `A` isn't sufficient (and I'm somewhat surprised it *was* so for gcc). – WhozCraig Jan 13 '15 at 09:32
  • @WhozCraig template declaration prior specialization also fixes it for VisualStudio 2012. But the question is why specialization for int (which is defined in global scope) doesn't accessible in main function, and the call foo() requires argument-dependent look-up to find function. – Vahagn Babajanyan Jan 13 '15 at 10:09

1 Answers1

3

I think Clang might be correct here. Consider two things:

§14.7.3/8 [temp.expl.spec], abridging the example somewhat:

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 ]

But, the friend function in question, foo, lives in, as per §11.3/7 [class.friend] (emphasis mine):

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

To provide a specialization of foo, it would have to be in A's lexical scope - which I do not think is possible to do. Your foo<int> specialization was in the wrong scope.

Note that this only applies if the function is defined in the class. The following compiles and runs for me on Clang just fine, because now goo is not in M's scope:

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

The visibility rules are established in §7.3.1.2/3 (emphasis mine):

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 ]

Thus, in this simpler example:

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

foo is defined in M, so it lives in its M's lexical scope. There is no "matching declaration" of foo outside of M so it should only be visible with ADL (§3.4.2/4, emphasis mine):

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+)
}

So I restate my first sentence: I think Clang may be correct here.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • But [class.friend] 11.3/6 says "A function can be defined in a friend declaration of a class if and only if the class is a non-local class (9.8), the function name is unqualified, **and the function has namespace scope**. [ Example: class M { friend void f() { } // definition of global f, a friend of M, // not the definition of a member function }; —end example ]". So I'm a bit confused, **what means lexical scope** in 11.3/7. Because specialization should be in enclosing scope in which primary template appears. And as for 11.3/6 inline friend definition has namespace scope. – Vahagn Babajanyan Jan 14 '15 at 06:22
  • @VahagnBabajanyan Found the relevant section about visibility rules. Hopefully that clarifies things. – Barry Jan 14 '15 at 12:39
  • @VahagnBabajanyan [Related question](http://stackoverflow.com/q/23171337/2069064). – Barry Jan 14 '15 at 12:50
  • @Barry, in "struct M { template void foo(T ) { } };" possibly you forgot `friend`. – Volodymyr Boiko Jan 31 '17 at 23:32
  • @GreenTree Thanks! Also fixed the comment in the last example to refer to the correct function, and point out which version of gcc failed. – Barry Jan 31 '17 at 23:40