2

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)

Walter
  • 44,150
  • 20
  • 113
  • 196

4 Answers4

2

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;
user7860670
  • 35,849
  • 4
  • 58
  • 84
  • Why should the definition be placed outside? – Walter Oct 12 '18 at 11:27
  • @Walter I've added some reference. – user7860670 Oct 12 '18 at 12:25
  • 1
    @Walter Compare the assembly for [this](https://godbolt.org/z/bRQFlV) and [this](https://godbolt.org/z/pACOlE). In the former case (definition outside), the compiler sees the template definition and can optimise everything. In the latter case (definition inside) the compiler cannot see the definition and thus issues a function call instead. This call would then lead to a linker error (as no code was emitted for the inside-definition). – Max Langhof Oct 12 '18 at 12:50
2

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

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.

Walter
  • 44,150
  • 20
  • 113
  • 196
  • _"as the forward declaration is still in place"_ -> A forward declaration can never resolve "function not defined" or "undefined symbol" errors, because to fix those errors you need a definition. As explained above, the in-class definition is insufficient, it needs to be outside. – Max Langhof Oct 12 '18 at 14:05
  • Also, to clarify: The friend declaration you used in the first snippet is only for that single `T` instantiation of `bar`, not the whole template. In other words, any instantiation of the `fun` class template will generate the according friend declaration _only_ for that particular `T`. In this case this wouldn't be a problem as using `bar` implicitly instantiates `fun` and thus the friend declaration. – Max Langhof Oct 12 '18 at 14:13
1

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.

ypnos
  • 50,202
  • 14
  • 95
  • 141
  • Yes, that's the same as I have found. Though why can one not move the definition of the function to the friend declaration? – Walter Oct 12 '18 at 11:26
  • 1
    My compiler complains about "defining explicit specialization ‘bar’ in friend declaration" and I guess it makes sense. – ypnos Oct 12 '18 at 11:30
  • There was once a time when clang was praised for more helpful error/warning output than GCC, I guess that time is over :P. – ypnos Oct 12 '18 at 11:33