【问题标题】:problems with template friend of template class模板类的模板友元问题
【发布时间】:2013-02-28 16:14:07
【问题描述】:

我遇到了在我看来 C++ 编译器不一致的问题。在下面的示例代码中

#include <vector>
namespace test {
  class A : std::vector<int>
  {
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
    template<int K, typename F>
    friend void foo(A const&a, F f) { for(auto i:a) if(i&K) f(i); }
  };
}
int sum(test::A const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );    // <-- error here
  bar   (a,[&s](int i) { s+=i; } );    // <-- but not here
  return s;
}

gcc(4.7.0,使用 std=c++11)抱怨“foo 未在此范围内声明”(并建议 test::foo 作为替代),但愉快地编译了 bar 的用法下一行。现在,foobar 都通过它们的 friend 声明注入到命名空间 test 中,因此它们都不应该真正存在于全局命名空间中。

Q1是我弄错了,还是这是 c++11 的新转折,还是 gcc 行为不端?

当然,如果我只是将 using 指令注入到全局命名空间中,就可以避免这个问题。但是,如果我将A 设为模板,

#include <vector>
namespace test {
  template<typename T>
  class A : std::vector<T>
  {
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
    template<int K, typename F>
    friend void foo(A const&a, F f) { for(auto i:a) if(i&K) f(i); }
  };
}
using test::foo;          // does not avoid compilation error
using test::bar;          // does not avoid compilation error
int sum(test::A<int> const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );
  bar   (a,[&s](int i) { s+=i; } );
  return s;
}

gcc 再次抱怨。要么(没有 using 指令)“foo 未在此范围内声明”(但再次愉快地编译 bar,但不建议 test::foo)或(使用 using 指令)“@在using 指令处,987654336@ 尚未声明”(test::bar 也是如此)。

Q2 这在我看来像是一个编译器错误,因为无论有没有using 指令,我都不能调用test::foo。或者也许我错过了一些关于 C++ 的东西?

最后,我尝试将朋友定义移到班级之外

namespace test {
  template<typename T>
  class A : std::vector<int>
  {
    template<int K, typename F>
    friend void foo(A const&a, F f);
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
  };

  template<int K, typename T, typename F>
  void foo(A<T> const&a, F f) { for(auto i:a) if(i&K) f(i); }

}
using test::foo;

当 gcc 再次抱怨时,这一次声称 void test::foo(const test::A&lt;T&gt;&amp;, F) 被使用但从未定义......所以 Q3 出了什么问题?

欢迎回答任何子问题。

【问题讨论】:

    标签: c++ templates c++11 namespaces friend


    【解决方案1】:

    第一季度:

    我是不是弄错了,或者这是 c++11 的新版本,还是 gcc 行为不端?

    不,这是正常行为。来自 C++11 标准的第 14.8.1/8 段:

    对于简单的函数名,依赖于参数的查找 (3.4.2) 适用于即使函数名 在调用范围内不可见。这是因为调用仍然具有函数的语法形式 调用 (3.4.1)。 但是当使用带有显式模板参数的函数模板时,调用没有 正确的语法形式,除非在调用时有一个具有该名称的函数模板可见。 如果没有这样的名称可见,则该调用在语法上不是格式正确的,并且依赖于参数的查找不会 申请。如果某些此类名称可见,则应用依赖于参数的查找和附加函数模板 可以在其他命名空间中找到。 [ 例子:

    namespace A {
        struct B { };
        template<int X> void f(B);
    }
    namespace C {
        template<class T> void f(T t);
    }
    void g(A::B b) {
        f<3>(b); // ill-formed: not a function call
        A::f<3>(b); // well-formed
        C::f<3>(b); // ill-formed; argument dependent lookup
        // applies only to unqualified names
        using C::f;
        f<3>(b); // well-formed because C::f is visible; then
        // A::f is found by argument dependent lookup
    }
    

    ——结束示例]


    第二季度:

    在我看来,这就像一个编译器错误,因为无论是否使用 using 指令,我都不能调用 test::foo。或者也许我错过了一些关于 C++ 的东西?

    如果你的类变成了一个你从不实例化的类模板,那么编译器将永远不会执行实例化A&lt;&gt;时会发生的第二阶段名称查找,因此它永远不会发现其中声明了两个friend 函数。

    例如,如果您在using 声明之前引入了模板的显式实例化,您应该会看到情况发生了变化:

    template class test::A<int>;
    

    或者,您可以只更改 A 的定义,使其仅声明,而不定义两个 friend 函数模板,并提供类外这些功能模板的定义。我猜这就是你实际尝试做的事情。但是……

    第三季度:

    gcc 再次抱怨,这一次声称 void test::foo(const test::A&, F) 被使用但从未定义......那么有什么问题?

    问题在于您没有将稍后定义的同一个函数声明为朋友:注意,您定义的函数需要一个额外的参数 (T)。修正你的声明,你会看到程序编译:

    namespace test 
    {
        template<typename T>
        class A : std::vector<int>
        {
            template<int K, typename C, typename F>
            //              ^^^^^^^^^^  (can't use T here, it would shadow
            //                           the class's template parameter)
            friend void foo(A<C> const&a, F f);
        };
    
        template<int K, typename C, typename F>
        void foo(A<C> const&a, F f) 
        { for(auto i:a) if(i&K) f(i); }
    }
    
    using test::foo; // Just don't remove this, or we will be back in Q1 ;-)
    

    结论:

    因此,经过所有必要的修改,您的程序将如下所示:

    #include <vector>
    
    namespace test
    {
        template<typename T>
        class A : std::vector<T>
        {
            template<typename F, typename C>
            friend void bar(A<C> const&a, F f);
    
            template<int K, typename F, typename C>
            friend void foo(A<C> const&a, F f);
        };
    
        template<typename F, typename C>
        void bar(A<C> const&a, F f) { for(auto i:a) f(i); }
    
        template<int K, typename F, typename C>
        void foo(A<C> const&a, F f) { for(auto i:a) if(i&K) f(i); }
    }
    
    using test::foo;
    using test::bar;
    
    int sum(test::A<int> const& a)
    {
        int s=0;
        foo<2>(a,[&s](int i) { s+=i; } );
        bar   (a,[&s](int i) { s+=i; } );
    
        return s;
    }
    

    【讨论】:

    • “应该看起来像”有点太强了,因为 using 可能不是 OP 真正想要的 - 在某些情况下,您真的希望 ADL 发生,理解它是什么很重要,并且它是如何工作的。
    • @DanielFrey:我把“应该”改成了“将”。
    • 另外,您对 Q2 的回答有点奇怪,因为编译器总是解析代码。无论如何,你的回答很有帮助,所以:+1
    • @DanielFrey:第一阶段不检查非限定名称。我会说得更准确。
    【解决方案2】:

    您的问题和问题的答案称为 ADL 以及应用它的规则。这在 C++11 中并不新鲜,在 GCC 中也不是问题。

    Q1:您有一个 a 类型的参数 test::A(在第一个示例中),因此 ADL(参数相关查找)在命名空间 test 中查找方法,但是 仅适用于非模板调用。这就是为什么找不到foo&lt;2&gt;(模板调用)而找到bar

    Q2:在 Q3 之后回答,见下文。

    Q3:您对test::foo 的函数定义没有定义您声明为test::A&lt;T&gt; 的朋友的函数。改成

    namespace test
    {
      template<typename T>
      class A;
    
      template<int K, typename F,typename T>
      void foo(A<T> const&a, F f);
    
      template<typename T>
      class A : std::vector<int>
      {
        template<int K, typename F,typename U>
        friend void foo(A<U> const&a, F f);
        template<typename F>
        friend void bar(A const&a, F f) { for(auto i:a) f(i); }
      };
    
      template<int K, typename F,typename T>
      void foo(A<T> const&a, F f) { for(auto i:a) if(i&K) f(i); }
    }
    using test::foo;
    

    Q2:和Q3类似,你可以这样修复:

    #include <vector>
    namespace test {
      template<typename T>
      class A;
    
      template<typename F,typename T>
      void bar(A<T> const&a, F f);
      template<int K, typename F,typename T>
      void foo(A<T> const&a, F f);
    
      template<typename T>
      class A : std::vector<T>
      {
        template<typename F,typename U>
        friend void bar(A<U> const&a, F f);
        template<int K, typename F,typename U>
        friend void foo(A<U> const&a, F f);
      };
    
      template<typename F,typename U>
      void bar(A<U> const&a, F f) { for(auto i:a) f(i); }
      template<int K, typename F,typename U>
      void foo(A<U> const&a, F f) { for(auto i:a) if(i&K) f(i); }
    }
    using test::foo;
    using test::bar;
    int sum(test::A<int> const&a)
    {
      int s=0;
      foo<2>(a,[&s](int i) { s+=i; } );
      bar   (a,[&s](int i) { s+=i; } );
      return s;
    }
    

    Andy 已经解释了为什么您的原始示例不起作用。

    【讨论】:

    • 嗯,bar 是一个模板函数。
    • @Walter。对,已编辑。另外,我会在第二季度/第三季度添加更多细节,因为我猜还有更多细节......
    • 我试过了(Q3),但得到了too many template-parameter-lists
    • @Walter:原件不正确(经过编辑,它是正确的)。另请参阅我对 Q2 的回答。
    猜你喜欢
    • 2010-12-19
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 2015-10-21
    • 1970-01-01
    • 2016-10-19
    • 2013-09-18
    相关资源
    最近更新 更多