Visibility of inline defined friend template function

Hi All,

A quick question regarding correct behaviour:

class Vec3 {
public:
friend void DoA(Vec3& a) {
a.m_int = 1;
}

template
friend void DoT(Vec3& a) {
if (B)
a.m_int = 2;
}
private:
int m_int;
};

void test_friend_functions_inline() {
Vec3 a;
DoA(a);
DoT(a); // error: use of undeclared identifier ‘DoT’
}

This compiles in MSVC but not clang. Any idea what’s doing the right thing? I’m running into it with squish-ccr: https://github.com/Ethatron/squish-ccr/blob/master/simd_sse.h which makes extensive use of the inline-friend-template-function pattern.

Thanks!
Will.

FYI: GCC also seems to accept the code.

Lookup of DoT here finds nothing, so this is parsed as a comparison
rather than as a template. EDG rejects this in its strict mode.

See [temp.arg.explicit]p8, which has almost exactly this case as an example.

Thanks Richard. [temp.arg.explicit]p8 uses namespaces in the example which threw me. Anyhow, I’ll take a look at adding support for this when in MS mode. I assume it shouldn’t be too hard to do…?

  • Will.

Thanks Richard. [temp.arg.explicit]p8 uses namespaces in the example which
threw me. Anyhow, I'll take a look at adding support for this when in MS
mode. I assume it shouldn't be too hard to do...?

Does MSVC perform friend injection in general, or does it just somehow
manage to parse DoT as a template name in this case? For instance:

struct S {
  friend void f() {}
  template<typename T> friend void g(T) {}
};
void h() {
  f(); // ok?
  g(S()); // ok?
  g<S>(S{}); // ok?
  g<int>(0); // ok?
}

... and what happens if you add:

  int f, g;

prior to the class definition? What happens if you add them to an
enclosing namespace?

Apologies, for the delay - this had to go on the backburner for a while. Here are my results with MSVC 10…

Test 1: Original

class S {
public:
friend void f() {}
template friend void g(T&) {}
};
void h() {
f(); // error C3767: ‘f’: candidate function(s) not accessible
// could be the friend function at ‘friend_functions_inline.cpp(58)’ : ‘f’ [may be found via argument-dependent lookup]

g(S()); // warning C4239: nonstandard extension used : ‘argument’ : conversion from ‘S’ to ‘S &’

g(S{}); // error C2143: syntax error : missing ‘)’ before ‘{’

g(0); // error C3767: ‘g’: candidate function(s) not accessible
// could be the friend function at ‘friend_functions_inline.cpp(59)’ : ‘g’ [may be found via argument-dependent lookup]
}

Test 2: Added variables f & g

int f, g;

class S {
public:
friend void f() {} // error C2365: ‘f’ : redefinition; previous definition was ‘data variable’
template friend void g(T&) {} // friend_functions_inline.cpp(61) : error C2365: ‘g’ : redefinition; previous definition was ‘data variable’
// friend_functions_inline.cpp(56) : see declaration of ‘g’
// friend_functions_inline.cpp(61) : error C2904: ‘g’ : name already used for a template in the current scope
// friend_functions_inline.cpp(56) : see declaration of ‘g’
};
void h() {
f(); // error C2064: term does not evaluate to a function taking 0 arguments
g(S()); // error C2064: term does not evaluate to a function taking 1 arguments
g(S{}); // [various errors - template not resolved]

g(0); // error C2062: type ‘int’ unexpected

}

Test 3: Enclosing namespace

This results in the same output as test 2 but with the namespace qualification in the errors.

I hope that provides some insight.

  • Will.

Apologies, for the delay - this had to go on the backburner for a while.
Here are my results with MSVC 10...

*Test 1: Original*

class S {
public:
   friend void f() {}
  template<typename T> friend void g(T&) {}
};
void h() {
  f(); // error C3767: 'f': candidate function(s) not accessible \
       // could be the friend function at
'friend_functions_inline.cpp(58)' : 'f' [may be found via
argument-dependent lookup]

  g(S()); // warning C4239: nonstandard extension used : 'argument' :
conversion from 'S' to 'S &'

Presumably this is accepted if you use g(T) not g(T&) on line 4?

  g<S>(S{}); // error C2143: syntax error : missing ')' before '{'

Is this accepted if you use S() not S{} ?

  g<int>(0); // error C3767: 'g': candidate function(s) not accessible \

             // could be the friend function at
'friend_functions_inline.cpp(59)' : 'g' [may be found via
argument-dependent lookup]
}

*Test 2: Added variables f & g*

int f, g;

class S {
public:
  friend void f() {} // error C2365: 'f' : redefinition; previous
definition was 'data variable'
  template<typename T> friend void g(T&) {} //
friend_functions_inline.cpp(61) : error C2365: 'g' : redefinition; previous
definition was 'data variable' \
   // friend_functions_inline.cpp(56) : see declaration of 'g' \
   // friend_functions_inline.cpp(61) : error C2904: 'g' : name already
used for a template in the current scope \
   // friend_functions_inline.cpp(56) : see declaration of 'g'
};
void h() {
  f(); // error C2064: term does not evaluate to a function
taking 0 arguments
  g(S()); // error C2064: term does not evaluate to a function taking 1
arguments
  g<S>(S{}); // [various errors - template not resolved]
  g<int>(0); // error C2062: type 'int' unexpected
}

*Test 3: Enclosing namespace*
*
*
This results in the same output as test 2 but with the namespace
qualification in the errors.

Some more test cases:

1)

int f;
namespace N {
  class S {
    template<typename T> friend void f(T) {}
  };
  void g() {
    f(S());
    f<S>(S());
    f(0);
    f<int>(0);

    int k = f;
  }
}

2)

class S {
  template<typename T> friend void f(T) {}
};
namespace N {
  int f;
  void g() {
    f(S());
    f<S>(S());
    f(0);
    f<int>(0);
  }
}

3)

namespace M {
  class S {
    template<typename T> friend void f(T) {}
  };
}
void g() {
  f(M::S());
  f<M::S>(M::S());
  f(0);
  f<int>(0);
}

I hope that provides some insight.