模板类的模板友的问题

problems with template friend of template class

本文关键字:问题      更新时间:2023-10-16

我遇到了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, using 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指令的点上"test::foo未声明"(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<T>&, F)但从未定义…那么Q3怎么了?

欢迎回答子问题

Q1:

是我错了,还是这是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
}

-end example]


Q2:

这看起来像一个编译器错误,因为无论是使用或不使用指令我可以调用test::foo。或者我可能错过了关于c++的一些东西?

如果你的类变成了一个你从未实例化过的类模板,那么编译器将永远不会执行在实例化A<>时发生的第二阶段名称查找,因此它将永远不会发现其中声明了两个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;
}

你的问题和问题的答案被称为ADL以及何时应用它的规则。这在c++ 11中不是新的,在GCC中也不是问题。

Q1:您有一个类型为test::A的参数a(在第一个示例中),因此ADL(参数依赖查找)在名称空间test中查找方法,但仅用于非模板调用。这就是为什么没有找到foo<2>(一个模板调用),而bar是。

Q2: Q3后回答,见下文

Q3:您的test::foo函数定义没有定义您声明为test::A<T>友元的函数。改为

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已经解释了为什么你原来的例子不起作用