[英]Template specializations in friend declarations post C++20
上下文:令我們驚訝的是,啟用了 C++20 模式和兩階段合規性的 MSVC 接受以下代碼:
template<class T>
class X
{
friend int foo<X>(X x);
int a = 10;
};
template <class T>
int foo(T t)
{
return t.a;
}
int main()
{
return foo(X<float>{});
}
這將在 MSVC 和 gcc(但 clang 吠)中編譯/鏈接並返回10
: https://godbolt.org/z/98cd7 。
那些知道兩階段查找的人會發現這看起來很瘋狂——我們可以在相應的模板被聲明之前以某種方式與模板特化成為朋友。 看起來和聽起來都非常錯誤。 事實上,對於早期的 C++ 版本,所有編譯器都會立即拒絕它: https://godbolt.org/z/M1733EPd5
但似乎 C++20 中的以下措辭為此鋪平了道路(或至少使它從一開始就不是完全錯誤的): https://timsong-cpp.github.io/cppwp/n4861/temp .names#2.sentence-4 (強調我的)
如果名稱是后跟
<
的非限定 ID,則名稱也被視為引用模板,並且名稱查找找到一個或多個函數或什么也沒找到。
現在,這導致了一大堆......好吧,荒謬的極端案例。 請注意,在friend
元聲明之外,您必須使用template<>
開始模板特化,但在friend
元聲明中並非如此。 可以預見的是,編譯器甚至無法就現在合法和不合法的內容達成一致:
結交... | 神螺栓 | MSVC | gcc | clang |
---|---|---|---|---|
...在 class 中聲明不存在的 function 模板的專業化,稱之為 | https://godbolt.org/z/9bczs6nde | ✓ | ✗ | ✗ |
...在未實例化的 class 模板中聲明不存在的 function模板的專業化 | https://godbolt.org/z/461h774sz | ✓ | ✓ | ✗ |
...declaration of specialization of non-existent function template inside a class template , call it (note https://timsong-cpp.github.io/cppwp/n4868/temp.inject ) | https://godbolt.org/z/fK6s9aj5G | ✓ | ✗ | ✗ |
...聲明class中不存在的function模板專業化的專業化,提供function模板定義,調用它 | https://godbolt.org/z/Mevr4EWzG | ✓ | ✗ | ✗ |
...聲明 class 模板中不存在的 function模板專業化的專業化,提供 function 模板定義,調用它 | https://godbolt.org/z/d9s9hWsa7 | ✓ | ✓ | ✗ |
...在 class 中定義先前聲明的 function 模板的專業化,稱之為 | https://godbolt.org/z/ebrhvGrM4 | 冰 | ✗ | ✓ |
...在 class 模板中定義先前聲明的 function模板的專業化,稱之為 | https://godbolt.org/z/bsjKxWYco | ✗ | ✗ | ✓ |
...在 class 模板中定義不存在的專業化 (!!) function 模板,然后調用它 (!!!) | https://godbolt.org/z/PnP6oKrrM | ✓ | ✗ | ✗ |
簡而言之,wtf。
問: C++20 標准目前是否對以下幾點有明確一致的立場:
朋友聲明引用以前未聲明的 function 模板是否合法? 這是否取決於我們是否在模板內/涉及依賴類型?
通過朋友聲明定義function 模板的特化是否曾經(或總是?)合法? (See again https://godbolt.org/z/bTaYraP6j , https://godbolt.org/z/Y7qf6PPxY - some compilers state it's illegal but then also randomly accept it)
我沒有檢查過 C++20,但我認為最新的標准草案說friend int foo<X>(X x);
的聲明格式不正確。
如果聲明是友元聲明:
- 如果 declarator 的declarator -id中的id-expression
E
是qualified-id或template-id :
- [...]
- 聲明符應對應於查找找到的一個或多個聲明
在friend int foo<X>(X x);
, declarator-id中的id-expression是foo<X>
。 它是一個template-id ,所以它需要有一個前面的聲明。
(作為參考,從定義點( [temp.res.general]/1 )查找foo
,因為它不是從屬名稱( [temp.dep.general]/2 )。)
確實這里沒有解析困難:C++20 確實為<
賦予了正確的含義,並且模板參數列表的解析獨立於模板聲明(這就是 function 模板重載和 ADL 可以工作的方式)。
接下來,已發布的 C++20 對聲明匹配的過程一般並沒有多說,留下了許多關於哪些聲明引用同一實體(有效或否)的問題。 因此,它並沒有真正解決聲明前的朋友的情況,盡管它的 [namespace.memdef]/3 說
如果友元聲明中的名稱既不是限定詞也不是模板 ID ,並且聲明是 function 或詳細類型說明符,則確定實體是否先前已聲明的查找不應考慮最內層封閉命名空間之外的任何范圍.
這意味着執行某種查找(包括在模板 ID情況下)可能會失敗。
我在上面說“如已發布”,因為已經提到的 P1787R6 中的許多修復被認為是缺陷報告(閱讀:追溯)。 我不記得關於這個主題的具體問題,但該論文中對[decl.meaning]的更改以指定聲明名稱的特殊查找過程可能最好也被視為追溯。
至於定義專業化,任何最近的標准版本所說的唯一內容是在 [temp.expl.spec]/1 中:
顯式特化 […] 可以通過
template<>
引入的聲明來聲明; […]
不幸的是,這很模糊,但暗示沒有其他方法可以定義明確的專業化,包括通過朋友聲明,這並不是不合理的。 當然這就是目的:通過name lookup找不到專業化,所以讓它們成為隱藏的朋友是沒有意義的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.