2013-02-28 75 views
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在下一行中。现在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有什么不对?

任何子问题的答案欢迎。

回答

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:

这在我看来就像一个编译器错误,因为既不与或如果不使用指令,我可以调用test :: foo。或者,也许我有一些关于C++的东西,我错过了?

如果你的类变成一个类模板你从来没有实例,那么编译器将不会执行实例A<>时可能会出现的第二阶段名称查找,所以它将它从来没有发现有两个friend在其中声明的函数。

如果推出,例如,您的模板之前using声明的显式实例,你应该看到的东西发生变化:

template class test::A<int>; 

或者,你可以只改变A定义,使其只有声明了,并且没有定义这两个函数模板,并为这些函数模板提供了类外定义。我猜,这是你实际上想要做的。但...

Q3:

GCC再次抱怨,这个时候声称无效测试:: foo的(常量测试::一个&,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; 
} 
+0

“应该看起来像”有点太强大了,因为“使用”可能不是OP真正想要的 - 在某些情况下,您确实希望ADL发生,理解它是什么以及它是如何工作是很重要的。 – 2013-02-28 17:30:37

+0

@DanielFrey:我将“should”改为“will”。 – 2013-02-28 17:32:40

+0

另外,您对Q2的回答有点奇怪,因为编译器总是*解析代码。无论如何,你的回答很有帮助,因此:+1 – 2013-02-28 17:33:06

1

您的问题和您的问题的答案称为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; 
} 

安迪已经解释了为什么你原来的例子不工作。

+0

uhm,'bar'是一个模板函数。 – Walter 2013-02-28 16:28:58

+0

@Walter。对,编辑。我还会在第二季度/第三季度增加更多的细节,因为我猜... – 2013-02-28 16:31:35

+0

我试过(** Q3 **),但得到了'太多的模板参数列表' – Walter 2013-02-28 17:02:29