解决模板友元功能的问题
problems with resolving friend function of template
我在让它工作时遇到了一些问题。这是我通过编译阶段的问题的 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)。但是,编译器现在在 linking/loading 阶段抱怨:
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
即使函数是在友元声明中定义的。相反,我将定义移到 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)
好的,我找到答案了:
为了使好友声明正常工作,它必须被限定为函数模板,即
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
我不太明白,因为前面的声明仍然存在。
这个为我编译:
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。我添加的是朋友声明的模板指定。
fun 内部的友元声明必须匹配函数模板的前向声明,否则会产生一个不相关的函数:
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;
}
In-class 永远不会使用函数定义:
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
;
友元函数有一些古怪的规则。
namespace X {
template<class T>
struct A{
friend void foo(A<T>) {}
};
}
上面的 foo
不是模板函数。它是一个非模板友元函数,存在于包含 A
的命名空间中,但只能通过 ADL 查找找到;不能直接命名为X::foo
.
很像模板的成员本身可以是模板,也可以不是,这是为 A
的每个模板 class 实例化创建的非模板友元函数。
namespace X{
template<class T>
void foo(A<T>);
}
这个 foo
是命名空间 X
中名为 foo
的函数模板。它与上面的非模板友元函数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
的好友。
我在让它工作时遇到了一些问题。这是我通过编译阶段的问题的 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)。但是,编译器现在在 linking/loading 阶段抱怨:
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
即使函数是在友元声明中定义的。相反,我将定义移到 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)
好的,我找到答案了:
为了使好友声明正常工作,它必须被限定为函数模板,即
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
我不太明白,因为前面的声明仍然存在。
这个为我编译:
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。我添加的是朋友声明的模板指定。
fun 内部的友元声明必须匹配函数模板的前向声明,否则会产生一个不相关的函数:
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;
}
In-class 永远不会使用函数定义:
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
;
友元函数有一些古怪的规则。
namespace X {
template<class T>
struct A{
friend void foo(A<T>) {}
};
}
上面的 foo
不是模板函数。它是一个非模板友元函数,存在于包含 A
的命名空间中,但只能通过 ADL 查找找到;不能直接命名为X::foo
.
很像模板的成员本身可以是模板,也可以不是,这是为 A
的每个模板 class 实例化创建的非模板友元函数。
namespace X{
template<class T>
void foo(A<T>);
}
这个 foo
是命名空间 X
中名为 foo
的函数模板。它与上面的非模板友元函数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
的好友。