簡體   English   中英

概念由看似會產生無效表達式的類型滿足

[英]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();
  }
};

https://godbolt.org/z/EqrMq4Moq

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM