模板类的模板朋友的问题

时间:2013-02-28 16:14:08

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

我遇到了一些看起来像是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作为替代),但很高兴地编译使用{@ 1}}在下一行。现在,barfoo都通过bar声明注入到命名空间test中,因此全局命名空间中都不应该存在。

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

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

A

gcc再次抱怨。要么(没有#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; } 指令)“using未在此范围内声明”(但又愉快地编译foo,但不建议bar)或(与test::foo指令)using指令点“test::foo尚未声明”(test::bar相同)。

Q2 这对我来说就像编译器错误一样,因为无论是否使用using指令都可以调用using。或者也许我错过了一些关于C ++的东西?

最后,我尝试将好友定义移到课堂外,如

test::foo

当gcc再次抱怨时,这次声称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; 被使用但从未定义过......所以 Q3 出了什么问题?

对任何子问题欢迎的答案。

2 个答案:

答案 0 :(得分:3)

<强> 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:

  

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

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

例如,如果您在 using声明之前引入了模板的显式实例化,那么您应该会看到一些变化:

template class test::A<int>;

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

<强> Q3:

  

gcc再次抱怨,这次声称使用了void test :: foo(const test :: A&amp;,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;
}

答案 1 :(得分:1)

您的问题和问题的答案称为ADL及其应用时间的规则。它在C ++ 11中并不新鲜,而且它不是GCC的问题。

Q1:您有a类型的参数test::A(在第一个示例中),因此ADL(参数相关查找)查找名称空间{{1}中的方法},但仅用于非模板调用。这就是test(模板调用)未找到且foo<2>为。

的原因

Q2:在第3季后回答,见下文。

Q3: bar的函数定义未定义您声明为test::foo的朋友的函数。将其更改为

test::A<T>

Q2:与Q3类似,您可以像这样修复:

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;

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