2013-02-28 55 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