4

我遇到了在我看来 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下一行中的用法。现在两者foo都通过它们的声明bar注入到命名空间中,因此它们都不应该真正存在于全局命名空间中。testfriend

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指令)“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有什么问题?

欢迎回答任何子问题。

4

2 回答 2

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
}

—结束示例]


Q2:

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

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

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

template class test::A<int>;

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

问题 3:

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;
}
于 2013-02-28T16:37:28.537 回答
1

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

Q1:您有一个a类型的参数test::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 已经解释了为什么您的原始示例不起作用。

于 2013-02-28T16:21:27.630 回答