這與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 UtilForDoingStuff
doStuff
重載已被定義似乎解決了問題。
但是標準的意思是「與函數參數的類型相關聯的名稱空間」是什麼意思?不應該將using ::MyClassThatCanDoStuff
聲明與名稱空間內的scoped_foo
實例類型的顯式範圍一起觸發與參數相關的查找,並且不應該找到doStuff()
的無主張定義?
此外,使用clang++ -ftemplate-delayed-parsing
編寫的代碼完全沒有錯誤,它模擬MSVC的模板解析行爲。至少在這種情況下,這似乎更可取,因爲隨時向名稱空間添加新聲明的能力是名稱空間的主要吸引力之一。但是,如上所述,根據標準,似乎並不遵循法律的規定。它是允許的,還是它是一個不合格的實例?
編輯::正如KIIV指出的,有一個解決方法;代碼將在編譯時使用模板特化,而不是重載。我仍然想知道關於標準問題的答案。
....呵呵。是的,這似乎工作(雖然模板專業化必須在涉及多個文件的非平凡情況下聲明爲「內聯」)。任何想法爲什麼超載版本不起作用,但專業化版本呢? –