[英]Concept is satisfied by a type that would seemingly produce an invalid expression
在下面的代碼中, can_foo
概念測試是否可以在類型的實例上調用foo()
成員 function。 我將使用它來測試兩個模板的實例: base
有條件地啟用foo
成員 function, derived
覆蓋foo
以調用其 base 的實現:
template <typename T>
concept can_foo = requires(T v) {
v.foo();
};
template <bool enable_foo>
struct base {
void foo()
requires enable_foo
{}
};
template <typename T>
struct derived : T {
void foo()
{
static_cast<T&>(*this).foo();
}
};
如果我測試base
模板的實例是否滿足這個概念,它會按照我的預期進行:
static_assert(can_foo<base<true>>); //okay
static_assert(not can_foo<base<false>>); //okay
當我將這些類型包裝在derived
中時,我看到:
static_assert(can_foo<derived<base<true>>>); //okay
static_assert(not can_foo<derived<base<false>>>); //error: static assertion failed
這太令人驚訝了! 我預計derived<base<false>>
不會滿足can_foo
- 它的foo
定義使用了一個在給定T = base<false>
時無效的表達式,並且在評估的上下文中使用概念測試的相同表達式導致一個說同樣多的錯誤:
int main()
{
derived<base<false>> v{};
v.foo(); //error
}
錯誤消息不在調用站點,這可能是相關的; 它引用了derived<>::foo
的主體。 來自 clang:
<source>:18:32: error: invalid reference to function 'foo': constraints not satisfied
static_cast<T&>(*this).foo();
^
<source>:31:7: note: in instantiation of member function 'derived<base<false>>::foo' requested here
v.foo(); //"invalid reference to function 'foo'"
^
<source>:10:18: note: because 'false' evaluated to false
requires enable_foo
^
clang: https://godbolt.org/z/vh58TTPxo gcc: https://godbolt.org/z/qMPrzznar
兩個編譯器產生相同的結果,所以我認為問題是我遺漏了標准中的微妙之處。 將can_foo<T>
約束添加到derived<T>
或derived<T>::foo
“修復”這個(即derived<base<false>>
將不再滿足can_foo
),在代碼審查中我會爭辯這個約束應該存在 - 但這是令人驚訝的行為 n.netheless 我想了解發生了什么。
那么:為什么derived<false>
滿足can_foo
?
requires-expression只能檢測被測試表達式的“直接上下文”中的無效構造。 尤其
requires(T v) {
v.foo();
};
不會檢查調用v.foo()
是否真的格式正確。 如果v.foo()
由於foo
function 主體內的結構不正確而導致格式不正確,則requires 表達式不會檢測到這一點,因為主體不在直接上下文中。
問題是,接下來會發生什么? requires-expression go 是否應該實例化foo
的主體並給出一個硬錯誤,或者它應該返回 true 並在稍后嘗試調用v.foo()
時給你一個硬錯誤? 答案是第二種:不進行實例化,因為不需要。 參見[temp.inst]/5
除非 function 模板特化是聲明的特化,否則當在需要 function 定義存在的上下文中引用特化時,或者如果定義的存在影響程序的語義,function 模板特化將被隱式實例化。 [...]
[temp.inst]/11 還意味着除非需要,否則不允許實現實例化定義。
在未計算的上下文中,調用v.foo()
不需要foo
的定義存在,因為它不是 odr-used 除非它可能被評估並且通常是 ODR 需要定義存在。 (但是,有兩種情況引用 function 需要其定義存在,即使在未評估的上下文中也是如此:當 function 具有推導的返回類型或需要進行常量評估時( [temp.inst]/8 ))。 由於不需要定義存在,因此不實例化定義。
您可能想要修改derived::foo
以便它從T::foo
傳播約束,因此可以通過can_foo<derived<T>>
檢測到格式錯誤:
void foo() requires can_foo<T> {
static_cast<T&>(*this).foo();
}
derived
對於每個模板參數T
都有一個foo
,它不能總是被實例化。 當嘗試實例化derived<base<false>>::foo
時,您嘗試在main
中調用它,您會收到錯誤消息,指出實例化無效,因為無法為類型base<false>
調用 foo。
關於何時實例化在標准中是可選的和/或必需的規則是[temp.inst] 。 然而,我無法指出一個結論性的段落,它說檢查derived<base<false>>::foo
是否存在於static_assert
的上下文中不需要實例化。 它與重載決議檢查相同嗎? “如果通過重載決議選擇的 function 可以在不實例化 class 模板定義的情況下確定,則未指定該實例化是否實際發生。”
如果你希望derived::foo
只有在它可以被實例化時才可用,你可以在它的 T 上要求can_foo
。
template <typename T>
struct derived : T {
void foo() requires can_foo<T>
{
static_cast<T&>(*this).foo();
}
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.