Walter
Walter

Reputation: 45414

problems with resolving friend function of template

I have some trouble getting this to work. Here is a MVCE of my problem that passes the compilation phase

template<typename T>
struct foo
{
    using type = T;
    friend type bar(foo const& x) { return x.X; }
    foo(type x) : X(x) {}
  private:
    type X;
};

template<typename> struct fun;
template<typename T> fun<T> bar(foo<T> const&, T);  // forward declaration

template<typename T>
struct fun
{
    using type = T;
    friend fun bar(foo<type> const& x, type y)
    { return {bar(x)+y}; }
  private:
    fun(type x) : X(x) {}
    type X;
};

int main()
{
    foo<int> x{42};
    fun<int> y = bar(x,7);    // called here
};

The forward declaration is required for the compiler to resolve the call in main() (see this answer for the reason). However, the compiler now complains in the linking/loading phase:

Undefined symbols for architecture x86_64: "fun bar(foo const&, int)", referenced from: _main in foo-00bf19.o ld: symbol(s) not found for architecture x86_64

even though the function is defined in the friend declaration. If instead, I move the definition outside of that of struct func<>, i.e.

template<typename T>
struct fun
{
    using type = T;
    friend fun bar(foo<type> const& x, type y);
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

compilation fails with

foo.cc:29:10: error: calling a private constructor of class 'fun<int>'
{ return {bar(x)+y}; }

So, how can I get this to work? (compiler: Apple LLVM version 9.0.0 (clang-900.0.39.2), c++11)

Upvotes: 2

Views: 171

Answers (4)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275330

There are some quirky rules around friend functions.

namespace X {
  template<class T>
  struct A{
    friend void foo(A<T>) {}
  };
}

foo above is not a template function. It is a non-template friend function which exists in the namespace enclosing A but it can only be found via ADL lookup; it cannot be directly named as X::foo.

Much like members of templates can be templates themselves, or not, this is a non-template friend function that is created for each template class instantiation of A.

namespace X{
  template<class T>
  void foo(A<T>);
}

this foo is a function template named foo in namespace X. It is not the same as the non-template friend function foo above.

From this, most of your errors are clear. What you thought was a forward declaration was an unrelated template function. What you thought was a friend, was not, so you didn't have access to the private constructor.

We can fix this a few ways. My favorite way is to add a tag type.

template<class T>struct tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag{};

now we can use tag to ADL dispatch:

template<typename T>
struct fun {
  using type = T;
  friend fun bar(tag_t<fun>, foo<type> const& x, type y)
  { return {bar(x)+y}; }
private:
  fun(type x) : X(x) {}
  type X;
};

and then in main this works:

foo<int> x{42};
fun<int> y = bar(tag<fun<int>>, x, 7);    // called here

But you might not want to mention tag<fun<int>>, so we just create a non-friend bar that does the call for us:

template<class T>
fun<T> bar(foo<T> const& x, type y)
{ return bar( tag<T>, x, y ); }

and now ADL does its magic and the proper non-template bar is found.

The other approach involves making the template function bar be a friend of fun.

Upvotes: 2

user7860670
user7860670

Reputation: 37487

Friend declaration inside of fun must match function template forward declaration, otherwise it will spawn an unrelated function:

template<typename TT> fun<TT> friend ::bar(foo<TT> const &, TT);

While definition should be placed outside:

template<typename T> fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

online compiler

The shorter code to demonstrate the problem would be:

void foo(void);

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

int main()
{
    foo(); // undefined reference to `foo()'
    return 0;
}

In-class function definition will never be used:

17.8.1 Implicit instantiation [temp.inst]

  1. The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class member functions, member classes, scoped member enumerations, static data members, member templates, and friends;

Upvotes: 2

ypnos
ypnos

Reputation: 52327

This one compiles for me:

template<typename T>
struct foo
{
    using type = T;
    friend type bar(foo const& x) { return x.X; }
    foo(type x) : X(x) {}
  private:
    type X;
};

template<typename> struct fun;
template<typename T> fun<T> bar(foo<T> const&, T);  // forward declaration

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<type>(foo<type> const& x, type y);
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

int main()
{
    foo<int> x{42};
    fun<int> y = bar(x,7);    // called here
};

Using GCC 8.2.1. What I added is the template designation to the friend declaration.

Upvotes: 1

Walter
Walter

Reputation: 45414

Okay, I found an answer:

In order for the friend declaration to work properly, it must be qualified as a function template, i.e.

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<T>(foo<type> const& x, type y);
    //            ^^^
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

However, combining the definition with the friend declaration still fails:

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<T>(foo<type> const& x, type y)
    { return {bar(x)+y}; }
  private:
    fun(type x) : X(x) {}
    type X;
};

results in:

foo.cc:20:16: warning: inline function 'bar<int>' is not defined [-Wundefined-inline]
    friend fun bar<T>(foo<T> const& x, T y)
               ^
1 warning generated.
Undefined symbols for architecture x86_64:
  "fun<int> bar<int>(foo<int> const&, int)", referenced from:
      _main in foo-c4f1dd.o
ld: symbol(s) not found for architecture x86_64

which I don't really understand as the forward declaration is still in place.

Upvotes: 1

Related Questions