Reputation: 45414
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
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
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}; }
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]
- 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
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
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