幾天來,我一直在使用和測驗我的應用程式,使用以下代碼沒有任何問題:
class dataHandler
{
public:
template<class T>
T GetData(bool truncate = false) { static_assert(false, "Unhandled output type"); }
template<T>
int GetData<int>(bool truncate)
{
// Normal stuff that compiles
}
}
只要不執行 GetData 默認值的隱式實體化,這(正如我當時所期望的)就可以正常作業。然而,今天在添加了一個新的失敗void*特化之后(我想要一個不同的錯誤),我發現它由于斷言而無法編譯,即使代碼中從未呼叫或提到過 `void* 規范。
我開始了一個新的 C 測驗專案(具有相同的 C 版本、MSVC 版本和 VS2022 版本),發現由于同樣的原因它也無法編譯:
template<class T>
T Spec()
{
static_assert(false, "default");
}
template<>
int Spec<int>()
{
return 1;
}
我無法在測驗專案中嘗試的任何內容中復制原始的“成功”,并且GetData<void*>初始專案中的失敗斷言似乎表明它不是專案配置/工具集問題的差異。
經過一番搜索,我發現它失敗是預期的(或其他預期的)行為,以及它的解決方法。
但是我發現自己想知道為什么最初的情況static_assert(false)也沒有編譯失敗。這是一些利基例外,還是 MSVC 的一致性問題?
uj5u.com熱心網友回復:
但是我發現自己想知道為什么 static_assert(false) 的初始情況也沒有編譯失敗。這是一些利基例外,還是 MSVC 的一致性問題?
這當然不是矛盾。這里的關鍵部分是“標準一致性模式”,可通過編譯器選項進行控制/permissive-。請參閱此編譯器選項的檔案。
VS2017之前的MSVC之所以接受你的static_assert(false, ...),是因為它推遲決議函式模板的內容,直到模板實體化。這就是決議器過去的作業方式,也是為什么像兩階段名稱查找這樣的功能永遠無法正確實作的原因。我將向您推薦此博客文章以獲取更多背景資訊。
您可以自己輕松嘗試。即使該函式包含甚至不應該正確決議的格式錯誤的代碼,只要模板沒有被實體化,編譯器就不會抱怨:
template<class T>
void foo()
{
Normally this should not (never actually (compile)) but it does anyway
}
它似乎做了一些基本的括號匹配,就是這樣。foo實體化時(通常在呼叫時)記錄和決議令牌流。
為了修復兩階段查找(和其他一致性問題),他們必須修復編譯器并在遇到函式模板時決議代碼,而不是實體化。但是現在他們遇到了一個問題,因為很多舊代碼可能依賴于突然不再編譯的舊編譯器行為。因此,他們/permissive-為用戶引入了選擇加入標準一致性模式的選項。并且逐漸為新專案或某些編譯器選項更改了默認值,如可以在檔案中閱讀的那樣:
/permissive- 選項從 Visual Studio 2019 版本 16.8 開始由 /std:c latest 選項隱式設定,在版本 16.11 中由 /std:c 20 選項隱式設定。/permissive- 是 C 20 模塊支持所必需的。也許您的代碼不需要模塊支持,但需要在 /std:c 20 或 /std:c latest 下啟用其他功能。您可以使用 /permissive 選項顯式啟用 Microsoft 擴展支持,而無需尾隨破折號。/permissive 選項必須位于任何隱式設定 /permissive- 的選項之后。
默認情況下,/permissive- 選項在由 Visual Studio 2017 版本 15.5 及更高版本創建的新專案中設定。在早期版本中默認情況下未設定。設定該選項后,當在您的代碼中檢測到非標準語言結構時,編譯器會生成診斷錯誤或警告。這些結構包括 C 11 之前代碼中的一些常見錯誤。
這使我們回答了您的問題。您沒有在原始代碼中看到它,因為您在編譯時沒有/permissive-. 在您在 VS2022 中創建的測驗專案中,/permissive-默認設定了模式,因此無法編譯。它也無法以顯式特化(您的void*情況)編譯,因為此時模板引數是已知的并且函式已實體化。
有幾種方法可以正確修復您的代碼。一種是明確洗掉主模板和您不想擁有的任何專業。
template<class T> void foo() = delete;
template<> void foo<void*>() = delete;
template<> void foo<int>()
{
// ...
}
這將使任何使用明確洗掉的變體格式錯誤,而沒有包含錯誤訊息的選項。當然void*,這個例子中的情況有點多余。
如果你想堅持static_assert,你必須使斷言的條件依賴于模板引數。如果你使用 C 17 編譯,你可以這樣做:
template<class...> constexpr bool always_false = false;
template<class T> void foo()
{
static_assert(always_false<T>, "your error here");
}
如果使用 C 14 或更早版本,您可以將其包裝always_false到結構模板中:
#include <type_traits>
template<class...> struct always_false : false_type { };
template<class T> void foo()
{
static_assert(always_false<T>::value, "your error here");
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/467080.html
