【问题标题】:problems with resolving friend function of template解决模板友元函数的问题
【发布时间】:2019-03-17 14:35:04
【问题描述】:

我在让它工作时遇到了一些麻烦。这是我通过编译阶段的问题的 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)",引用自: foo-00bf19.old 中的 _main:未找到架构 x86_64 的符号

即使函数是在友元声明中定义的。相反,我将定义移到struct func&lt;&gt; 之外,即

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)

【问题讨论】:

    标签: c++ templates friend


    【解决方案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}; }
    

    online compiler

    演示问题的较短代码是:

    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]

    1. 类模板特化的隐式实例化会导致声明的隐式实例化,但不会导致类成员函数、成员类、作用域成员枚举、静态数据成员、成员的定义、默认参数或 noexcept 说明符的隐式实例化模板和friends

    【讨论】:

    • 为什么要把定义放在外面?
    • @Walter 我添加了一些参考。
    • @Walter 比较 thisthis 的程序集。在前一种情况下(外部定义),编译器看到模板定义并可以优化所有内容。在后一种情况下(内部定义),编译器看不到定义,因此发出函数调用。然后,此调用将导致链接器错误(因为没有为内部定义发出代码)。
    【解决方案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 是命名空间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&lt;fun&lt;int&gt;&gt;,所以我们只需创建一个非朋友bar 来为我们打电话:

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

    现在 ADL 发挥了作用,找到了正确的非模板 bar

    另一种方法是让template 函数bar 成为fun 的朋友。

    【讨论】:

      【解决方案3】:

      好的,我找到了答案:

      为了使朋友声明正常工作,它必须被限定为一个函数模板,即

      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
      

      我不太明白,因为前向声明仍然存在。

      【讨论】:

      • “因为前向声明仍然存在” -> 前向声明永远无法解决“函数未定义”或“未定义符号”错误,因为要修复这些错误你需要一个定义。如上所述,类内定义是不够的,需要在外。
      • 另外,澄清一下:您在第一个 sn-p 中使用的朋友声明仅用于 bar 的单个 T 实例化,而不是整个模板。换句话说,fun 类模板的任何实例化都会为特定的T 生成相应的朋友声明only。在这种情况下,这不会成为问题,因为使用 bar&lt;T&gt; 隐式实例化 fun&lt;T&gt; 并因此实例化朋友声明。
      【解决方案4】:

      这是为我编译的:

      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。我添加的是朋友声明的模板名称。

      【讨论】:

      • 是的,和我发现的一样。虽然为什么不能将函数的定义移到朋友声明中?
      • 我的编译器抱怨“在朋友声明中定义显式特化‘bar’”,我想这是有道理的。
      • 曾有一段时间,clang 被称赞为比 GCC 更有用的错误/警告输出,我想那个时代已经结束了:P。
      猜你喜欢
      • 1970-01-01
      • 2010-12-19
      • 1970-01-01
      • 2011-07-15
      • 1970-01-01
      • 2021-11-11
      • 1970-01-01
      • 2016-10-19
      相关资源
      最近更新 更多