我很难使它正常工作。这是我的问题的MVCE,已经通过了编译阶段
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
};
编译器需要前向声明才能解析main()
中的调用(原因请参见this answer)。但是,编译器现在在链接/加载阶段抱怨:
体系结构x86_64的未定义符号:“有趣 bar(foo const&,int)“,引用自: _main在foo-00bf19.o ld中:找不到针对架构x86_64的符号
即使该函数在friend声明中定义。如果相反,我将定义移到struct func<>
之外,即
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}; }
编译失败,
foo.cc:29:10: error: calling a private constructor of class 'fun<int>'
{ return {bar(x)+y}; }
那么,我该如何工作呢? (编译器:Apple LLVM版本9.0.0(clang-900.0.39.2),c ++ 11)
答案 0 :(得分:2)
好玩的朋友声明必须与函数模板的正向声明匹配,否则会生成不相关的函数:
template<typename TT> fun<TT> friend ::bar(foo<TT> const &, TT);
定义应放在外部:
template<typename T> fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }
演示该问题的较短代码是:
void foo(void);
template<typename T>
struct bar
{
friend void foo(void) {}
};
int main()
{
foo(); // undefined reference to `foo()'
return 0;
}
绝不会使用类内函数定义:
17.8.1隐式实例化[temp.inst]
- 类模板特殊化的隐式实例化导致声明的隐式实例化,但不隐含类成员函数,成员类,作用域成员枚举,静态数据成员,成员的定义,默认参数或noexcept-specifiers模板和
friends
;
答案 1 :(得分:2)
关于朋友功能有一些古怪的规则。
namespace X {
template<class T>
struct A{
friend void foo(A<T>) {}
};
}
上面的 foo
不是模板函数。它是一个非模板的朋友功能,存在于A
内的命名空间中,但只能通过ADL查找来找到;它不能直接命名为X::foo
。
与模板成员很可能本身就是模板一样,这是一个非模板的朋友函数,是为A
的每个模板类实例创建的。
namespace X{
template<class T>
void foo(A<T>);
}
此foo
是命名空间foo
中名为X
的功能模板。它与上面的非模板朋友功能foo
不相同。
由此,您的大多数错误都显而易见。您认为的前向声明是一个不相关的模板函数。您以为是朋友,不是,所以您无权访问私有构造函数。
我们可以通过几种方法解决此问题。我最喜欢的方法是添加标签类型。
template<class T>struct tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag{};
现在我们可以使用tag
进行ADL调度:
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;
};
然后在主要方面起作用:
foo<int> x{42};
fun<int> y = bar(tag<fun<int>>, x, 7); // called here
但是您可能不想提及tag<fun<int>>
,所以我们只创建一个非朋友bar
来替我们打电话:
template<class T>
fun<T> bar(foo<T> const& x, type y)
{ return bar( tag<T>, x, y ); }
现在ADL发挥了魔力,并且找到了正确的非模板bar
。
另一种方法涉及使template
函数bar
成为fun
的朋友。
答案 2 :(得分:1)
好的,我找到了答案:
为使好友声明正常工作,必须将其声明为函数 template ,即
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}; }
但是,将定义与好友声明结合在一起仍然失败:
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;
};
导致:
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
我还不太了解,因为前向声明仍然存在。
答案 3 :(得分:1)
这个为我编译:
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
};
使用GCC 8.2.1。我在好友声明中添加了模板名称。