2015-08-26 45 views
2

這與this question非常相似,但我不確定這個答案是否完全適用於我放在一起的證明該問題的最小代碼。 (我的代碼是而不是使用尾隨返回類型,並且還有一些其他差異。)此外,MSVC的行爲是否合法的問題似乎沒有解決。命名空間導致次優模板重載分辨率

總之,當函數模板位於命名空間中時,我看到編譯器選擇了一個通用函數模板實例,而不是一個更具體的重載。

考慮下面的一組命名空間和類定義:

namespace DoStuffUtilNamespace 
{ 
    template<typename UNKNOWN> 
    void doStuff(UNKNOWN& foo) 
    { 
    static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!"); 
    } 
} 

class UtilForDoingStuff 
{ 
    public: 
    template <typename UNKNOWN> 
     void doStuffWithObjectRef(UNKNOWN& ref) 
     { 
     DoStuffUtilNamespace::doStuff(ref); 
     } 
}; 

class MyClassThatCanDoStuff { }; 

namespace DoStuffUtilNamespace 
{ 
    using ::MyClassThatCanDoStuff;  // No effect. 

    void doStuff(MyClassThatCanDoStuff& foo) { /* No assertion! */ } 
} 

...及以下的用例:

int main() 
{ 
    MyClassThatCanDoStuff foo; 
    DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo; 
    UtilForDoingStuff util; 

    DoStuffUtilNamespace::doStuff(foo);   // Compiles 
    DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles 
    util.doStuffWithObjectRef(foo);    // Triggers static assert 
    util.doStuffWithObjectRef(scoped_foo);  // Triggers static assert 
} 

如果整個DoStuffUtilNamespace消除其所有成員都感動到全球範圍,這與G ++和Clang ++編譯得很好。

命名空間doStuff當然是一個獨立名稱。按照top-voted answer on the similar question,標準說:

在解決相關的名稱,從下列來源的名稱被認爲是:

  • 聲明是在模板的定義點可見。

  • 來自命名空間的聲明與來自實例化上下文和來自定義上下文的函數參數類型相關聯。

這似乎有點奇怪我。我不明白爲什麼第一點會指定該聲明必須在模板的定義點,而不是在實例點是可見的,因爲第二個項目符號點明確規定,一些聲明僅在實例化處可見的是允許的。 (如果有人想提供一個理由,我會很感激,但這不是我的問題,因爲我的理解是,「爲什麼標準委員會決定X」的形式問題是脫離主題的。)

因此,我認爲解釋了爲什麼util.doStuffWithObjectRef(foo);觸發靜態斷言:doStuff(MyClassThatCanDoStuff&)尚未在UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&)的定義點處聲明。確實在之後定義class UtilForDoingStuffdoStuff重載已被定義似乎解決了問題。

但是標準的意思是「與函數參數的類型相關聯的名稱空間」是什麼意思?不應該將using ::MyClassThatCanDoStuff聲明與名稱空間內的scoped_foo實例類型的顯式範圍一起觸發與參數相關的查找,並且不應該找到doStuff()的無主張定義?

此外,使用clang++ -ftemplate-delayed-parsing編寫的代碼完全沒有錯誤,它模擬MSVC的模板解析行爲。至少在這種情況下,這似乎更可取,因爲隨時向名稱空間添加新聲明的能力是名稱空間的主要吸引力之一。但是,如上所述,根據標準,似乎並不遵循法律的規定。它是允許的,還是它是一個不合格的實例?

編輯::正如KIIV指出的,有一個解決方法;代碼將在編譯時使用模板特化,而不是重載。我仍然想知道關於標準問題的答案。

回答

2

對於名稱空間,doStuff當然是一個從屬名稱。

你是從錯誤的前提開始的。沒有像DoStuffUtilNamespace::doStuff(ref)這樣的合格呼叫的ADL。 [basic.lookup。argdep]/P1,重點煤礦:

後綴表達式在一個函數調用(5.2.2)是 不合格-ID,其他名稱空間不會在通常的 不合格考慮可能會搜索查找(3.4.1),並且在這些名稱空間中,可能會找到 名稱空間範圍的朋友函數或函數模板聲明 (11.3)。

DoStuffUtilNamespace::doStuff合格-ID,而不是一個不合格-ID。 ADL不適用。因此,DoStuffUtilNamespace::doStuff也不是依賴名稱。 [temp.dep]/P1:

在形式的表達:

後綴表達式(表達式列表選擇

其中後綴 - 表達式不合格id-, 不合格id-表示從屬名稱如果[...]。如果運算符的操作數是與類型相關的表達式,則運算符也表示 依賴名稱。這些名稱綁定,並在 點模板實例(14.6.4.1)中的 模板定義兩者的背景和實例化點的上下文中查找

(的依賴的斜體命名表明,本款定義的術語)

相反,每[temp.nondep]/P1:

在模板定義中使用

非依賴名字使用發現通常的名稱查找和綁定在他們使用的位置。

找不到您以後的重載聲明。


專業化的作品,因爲它仍然是使用相同的功能模板聲明;你只是提供了一個不同於默認實現的實現。


但究竟是什麼標準的意思是「與 類型的函數參數相關聯的命名空間」?應該不是using ::MyClassThatCanDoStuff聲明,與scoped_foo實例類型的 命名空間,觸發參數依賴查找

號using聲明■不要影響ADL中明確的範圍界定一起 。 [basic.lookup.argdep]/P2,重點煤礦:

對於在函數調用每個參數類型T,有一組 零個或多個相關聯的命名空間和一組零個或多個相關聯的 類要考慮。命名空間和 類的集合完全由函數參數 (以及任何模板模板參數的命名空間)的類型決定。 typedef名稱以及使用聲明用於指定類型就做無助於這一套。命名空間和類的組被 以如下方式確定:

  • 如果T是一個基本的類型,[...]

  • 如果T是一個類類型(包括工會),其相關的類是:類本身;它是其成員的類別(如果有的話);和它的 直接和間接基類。其關聯的名稱空間是其相關類的最內層封閉名稱空間。此外,如果T是類模板專業化,則其關聯的名稱空間和類別還包括:與爲模板類型參數 (不包括模板模板參數)提供的模板參數類型相關聯的名稱空間和類。任何 模板模板參數都是成員的名稱空間;以及用作模板模板參數的任何 成員的類都是成員。 [ 注意:非類型的模板參數不貢獻於關聯的命名空間的集合。 - 注完]

  • [...]

1

隨着模板特殊化,我可以得到它的工作:從同類型的無論是從實例化上下文和自定義背景功能參數相關的命名空間

namespace DoStuffUtilNamespace 
{ 
    template<typename UNKNOWN> 
    void doStuff(UNKNOWN& foo) 
    { 
    static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!"); 
    } 
} 

class UtilForDoingStuff 
{ 
    public: 
    template <typename UNKNOWN> 
     void doStuffWithObjectRef(UNKNOWN& ref) 
     { 
     DoStuffUtilNamespace::doStuff(ref); 
     } 
}; 

class MyClassThatCanDoStuff { }; 


namespace DoStuffUtilNamespace 
{ 
    using ::MyClassThatCanDoStuff; 
    template <> void doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo) { /* No assertion! */ } 
} 


int main() 
{ 
    MyClassThatCanDoStuff foo; 
    DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo; 
    UtilForDoingStuff util; 

    DoStuffUtilNamespace::doStuff(foo);   // Compiles 
    DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles 
    util.doStuffWithObjectRef(foo);    // Compiles 
    util.doStuffWithObjectRef(scoped_foo);  // Compiles 
} 
+0

....呵呵。是的,這似乎工作(雖然模板專業化必須在涉及多個文件的非平凡情況下聲明爲「內聯」)。任何想法爲什麼超載版本不起作用,但專業化版本呢? –

0

聲明。

用下面的代碼

例主叫?::foo(const B::S&)當它打印B::fooDemo

namespace A 
{ 
    template <typename T> 
    void foo(const T&) {std::cout << "A::foo" << std::endl;} 

    template <typename T> 
    void bar(const T& t) { 
     foo(t); // thank to ADL, it will also look at B::foo for B::S. 
    } 
} 

namespace B 
{ 
    struct S {}; 

    void foo(const S&) {std::cout << "B::foo" << std::endl;} 
} 

int main() 
{ 
    B::S s; 
    A::bar(s); 
} 

所以,第二個項目符號點增加B::foo過載能力的列表。

爲什麼模板專業化在這種情況下

工作只有一個功能:

template<> 
void DoStuffUtilNamespace::doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo); 
哪怕是後面定義

。 請注意,在翻譯單元中應該知道存在專業化的事實,否則程序會生病(不尊重ODR)。

雖然重載沒有。

你想:

所以,我認爲這可以解釋爲什麼util.doStuffWithObjectRef(foo);觸發靜態斷言:doStuff(MyClassThatCanDoStuff&)還沒有在UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&)定義的點被宣佈。確定在doStuff過載之後確定移動類UtilForDoingStuff的定義似乎解決了這個問題。

沒錯。