![](/img/trans.png)
[英]Why does this templated struct with C++17 `constexpr if` fail to compile on MSVC?
[英]"If constexpr" in C++17 does not work in a non-templated function
我嘗試使用 C++17 標准。 if constexpr
,我嘗試使用 C++17 的功能之一。 我有一個問題......請看下面的代碼。 這編譯沒有錯誤。 在下面的代碼中,我嘗試使用if constexpr
來檢查它是否是一個指針。
#include <iostream>
#include <type_traits>
template <typename T>
void print(T value)
{
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Ok
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
auto n = 1000;
print(n);
print(&n);
}
但是當我重寫上面的代碼時,如下圖,其中if constexpr
在main
function中:
#include <iostream>
#include <type_traits>
int main()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
我得到一個編譯錯誤:
main.cpp:8:32: error: invalid type argument of unary '*' (have 'int') std::cout << "Ptr to " << *value << std::endl;
問題不在主要 function 上。 這可以是類似於以下的任何 function。
void print()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
print();
}
我想知道為什么if constexpr
僅適用於模板函數,即使類型是由輸入參數中的 decltype 推導出來的。
我想知道為什么“
if constexpr
”僅適用於模板函數,即使類型是由輸入參數的decltype
推導出來的。
這是設計使然。
if constexpr
不會實例化未采用的分支,如果它在模板中。 它不會只是將沒有被視為令牌湯的分支視為完全解析或執行語義分析。 雙方仍將被分析,並且由於*value
對於int
s int
錯誤,這是一個錯誤。
您根本無法使用if constexpr
來避免編譯非模板代碼。 這只是為了避免實例化可能對特定專業化無效的模板代碼。
C++ 標准,條款 9.4.1:
如果 if 語句的形式為 if constexpr,則條件的值應為 bool (8.6) 類型的上下文轉換常量表達式; 這種形式稱為 constexpr if 語句。 如果轉換條件的值為假,則第一個子語句是丟棄的語句,否則第二個子語句(如果存在)是丟棄的語句。 在封閉模板化實體的實例化期間(第 17 條),如果條件在實例化后不依賴於值,則不會實例化丟棄的子語句(如果有)。
(強調我的)
因此, constexpr if
的子constexpr if
不在模板內,它仍然會被實例化,因此它至少必須編譯。
在模板之外,完全檢查丟棄的語句。 if constexpr 不能替代 #if 預處理指令。
我想知道為什么
if constexpr
僅適用於模板函數,即使類型是由輸入參數的 decltype 推導出來的。
問題是,它也適用於非模板,只是不像您期望的那樣。
if constexpr
像你說的那樣工作,你不僅需要一個模板,而且你需要包含的表達式依賴於模板參數。
讓我們一步一步地了解為什么在 C++ 中采用這種方式,以及其含義是什么。
讓我們從簡單的開始。 下面的代碼可以編譯嗎?
void func_a() {
nonexistant();
}
我想我們都會同意它不會編譯,我們正在嘗試使用尚未聲明的函數。
讓我們添加一層。
下面的代碼可以編譯嗎?
template<typename T>
void func_b() {
nonexistant();
}
使用正確的編譯器,這將無法編譯。
但這是為什么呢? 您可能會爭辯說這段代碼從未真正編譯過,因為模板從未被實例化。
該標准定義了他們稱之為兩階段名稱查找的東西。 這是即使模板沒有實例化,編譯器也必須執行名稱查找和任何不依賴於模板參數的內容。
這是有道理的。 如果表達式nonexistant()
不依賴於T
,為什么它的含義會隨着T
改變? 因此,在編譯器的眼中,此表達式與func_a
中的表達式相同。
現在,輸入if constexpr
。
為了使這種結構與語言的其余部分正常工作,已經決定if constexpr
定義為實例化的分支。 因此,我們可以使一些代碼非實例化,即使在非模板中!
extern int a;
void helper_1(int*);
void func_c() {
if constexpr (false) {
helper_1(&a);
}
}
答案是helper_1
和a
沒有使用 ODR。 我們可以保留helper_1
和a
未定義,這樣就不會出現鏈接器錯誤。
更好的是,編譯器不會實例化if constexpr
的丟棄分支中的模板:
template<typename T>
void helper_2() {
T::nonexistant();
}
void func_d() {
if constexpr (false) {
helper_2<int>();
}
}
這段代碼不會用正常的if
編譯。
如您所見, if constexpr
的丟棄分支就像一個尚未實例化的模板一樣工作,即使在非模板代碼中也是如此。
現在讓我們混合一下:
template<typename T>
void func_b_2() {
if constexpr (false) {
nonexistant();
}
}
這就像我們一開始的模板函數一樣。 我們說過即使模板沒有被實例化,代碼也是無效的,因為無效的表達式不依賴於T
。 我們也說過, if constexpr
是實例化過程中的一個分支。 錯誤發生在實例化之前。 這段代碼也不會編譯。
所以最后,這段代碼也不會編譯:
void func_e() {
if constexpr (false) {
nonexistant();
}
}
即使沒有實例化if constexpr
的內容, if constexpr
發生錯誤,因為完成了第一個名稱查找步驟,並且錯誤發生在實例化過程之前。 只是在這種情況下,沒有實例化,但此時無所謂。
那么if constexpr
什么用途呢? 為什么它似乎只適用於模板?
問題是,它在模板中的工作方式並沒有什么不同。 正如我們在func_b_2
看到的func_b_2
,錯誤仍然發生。
但是,這種情況將起作用:
template<typename T>
void helper_3() {
if constexpr (false) {
T::nonexistant();
}
}
void func_f() {
helper_3<int>();
}
表達式int::nonexistant()
無效,但代碼可以編譯。 這是因為由於T::nonexistant()
是一個依賴於T
的表達式,名稱查找在第二階段完成。 名稱查找的第二階段在模板實例化期間完成。 包含T::nonexistant()
的if constexpr
分支總是被丟棄的部分,因此名稱查找的第二階段永遠不會完成。
你去吧。 if constexpr
不是不編譯一部分代碼。 就像模板一樣,它們被編譯並完成任何可以進行名稱查找的表達式。 if constexpr
是關於控制實例化,即使在非模板函數中。 適用於模板的所有規則也適用於if constexpr
所有分支。 兩階段名稱查找仍然適用,並允許程序員不實例化如果實例化則不會編譯的某些代碼部分。
因此,如果代碼無法在未實例化的模板中編譯,則不會在未實例化的if constexpr
分支中編譯。
我參加聚會有點晚了,但是當我需要非模板 function 中的if constexpr
是將我的代碼包裝在 lambda 中時,我會使用一個不錯的技巧。
void foo()
{
/* foo is not template, wont compile
if constexpr ( false )
{
std::cout << Empty{}.bar;
}
*/
// now the code is in a lambda with auto param, so basicly a template method
[&](const auto& empty)
{
if constexpr ( false )
{
std::cout << empty.bar;
}
}(Empty{});
}
演示: https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO
我可以使用[&]
語法的事實使這種解決方案的使用方式比創建輔助方法更簡單,因為我不需要在if constexpr
中轉發我需要的所有參數
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.