繁体   English   中英

模板类的模板朋友的问题

[英]problems with template friend of template class

我遇到了一些看起来像是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行为不端?

当然,如果我只是使用指令注入全局命名空间,就可以避免这个问题。 但是,如果我制作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指令)“ test::foo尚未声明“(和test::bar相同)在using指令处。

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
}

- 末端的例子]


Q2:

这看起来像编译器错误,因为使用或不使用using指令都可以调用test :: foo。 或者也许我错过了一些关于C ++的东西?

如果您的类成为您从未实例化的类模板 ,那么编译器将永远不会执行在实例化A<>时发生的第二阶段名称查找,因此它将永远不会发现在其中声明了两个friend函数。

例如,如果您在using声明之前引入了模板的显式实例化,那么您应该看到更改的内容:

template class test::A<int>;

或者,您可以只更改A的定义,使其仅声明和不定义两个friend函数模板,并为这些函数模板提供类外定义。 我想,这就是你真正试图做的事情。 但...

Q3:

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;
}

安迪已经解释了为什么你的原始例子不起作用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM