<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      C++ 模板參數(shù)推導(dǎo)問(wèn)題小記(非推導(dǎo)上下文)

      最近遇到一個(gè)模板參數(shù)推導(dǎo)的問(wèn)題,代碼如下:

      代碼
      
      template<typename T>
      using scalar = std::enable_if_t<std::is_arithmetic_v<T>, T>;
      template<typename T>
      void foo(scalar<T> val)
      {
        ...
      }
      foo(5);
      

      這是我突發(fā)奇想寫(xiě)出來(lái)的,模板別名 scalar 限制函數(shù)參數(shù)為數(shù)值類(lèi)型,可以在多處復(fù)用,這個(gè)代碼無(wú)法通過(guò)編譯,編譯器提示沒(méi)有匹配的函數(shù)調(diào)用。

      代碼很簡(jiǎn)單,看起來(lái)也沒(méi)什么不妥,為什么出錯(cuò)了?這個(gè)看起來(lái)簡(jiǎn)單的問(wèn)題同樣難倒了幾個(gè)常用的 AI 編程助手(AI 的回答放在文章最后一節(jié))。

      問(wèn)題分析

      正常的模板實(shí)例化過(guò)程中,編譯器結(jié)合模板形參模式和實(shí)例化時(shí)提供的參數(shù)類(lèi)型,確定一個(gè)或一組模板實(shí)參類(lèi)型,將這些實(shí)參替換到形參后能夠形成與實(shí)例化參數(shù)相匹配的參數(shù)列表。

      在 foo(5) 這一調(diào)用中,形參是 scalar<T>, 實(shí)例化參數(shù)是 int 類(lèi)型,編譯器需要確定一個(gè)類(lèi)型 T,使得scalar<T> 匹配 int。

      我們來(lái)理一下這個(gè)過(guò)程:

      deduct

      我們會(huì)說(shuō),這不是一眼就能看出 T 就是 int 嘛,std::enable_if<std::is_arithmetic<T>::value, T>::type成功實(shí)例化的結(jié)果就是 T 本身。但是站在編譯器的角度來(lái)看,可不能這樣下定論,有些情況下,這部分可能并不是一個(gè)可以反推出固定類(lèi)型的模板,舉一個(gè)最簡(jiǎn)單的例子:

      代碼
      
      template<typename T>
      struct wrapper
      {
        using type = int;
      };
      template<typename T>
      using scalar_confused = typename wrapper<T>::type;
      template<typename T>
      void foo_confused(scalar_confused<T> val)
      {
        ...
      }
      foo_confused(5);
      

      這個(gè)模板 wrapper 無(wú)論用什么類(lèi)型實(shí)例化都能取到 int,也就是說(shuō)在反推 T 時(shí)無(wú)法確定一個(gè)唯一的類(lèi)型,這對(duì)于編譯器來(lái)說(shuō)是無(wú)法處理的,于是它實(shí)例化不出任何 foo_confused 的實(shí)例。

      其實(shí) C++ 標(biāo)準(zhǔn)已經(jīng)對(duì)這類(lèi)問(wèn)題作出了說(shuō)明,官方的命名是非推導(dǎo)上下文(non-deduced context):

      non_deduced context

      我們代碼的問(wèn)題就是上圖指出的這種情況,如果模板參數(shù)只出現(xiàn)在嵌套名稱(chēng)說(shuō)明符內(nèi)(即 :: 符號(hào)左邊的部分),編譯器將不會(huì)嘗試從實(shí)例化參數(shù)中推導(dǎo)該模板參數(shù),只能使用已經(jīng)推導(dǎo)出的或顯式指定的參數(shù)類(lèi)型。

      StackOverflow 上這篇文章(What is a non deduced context?)還有它提到的一些鏈接把這個(gè)概念講的很清楚。

      如果把 scalar 直接展開(kāi)到使用位置,我們的代碼等價(jià)于:

      代碼
      
      template<typename T>
      void foo(typename std::enable_if<std::is_arithmetic<T>::value, T>::type val)
      {
        ...
      }
      

      這樣看來(lái)問(wèn)題就清晰了,我們拐了一個(gè)彎創(chuàng)造了一個(gè)非推導(dǎo)上下文。編譯器在解析到這一步時(shí),就已經(jīng)拒絕后續(xù)的推導(dǎo)了,后面我們關(guān)于反推的分析實(shí)際都沒(méi)有發(fā)生。

      如何解決

      問(wèn)題找到了,那么應(yīng)該如何解決呢?把推導(dǎo)移到模板參數(shù)列表里面,讓它在模板參數(shù)替換時(shí)先推導(dǎo)出來(lái),后面再引用行不行:

      代碼
      
      template<typename T, typename S = std::enable_if_t<std::is_arithmetic_v<T>, T>>
      using scalar = S;
      

      可惜還是不行,而且增加了一層間接,編譯器仍舊會(huì)失敗在相同的位置:

      pre_deduce

      其實(shí)解決方法很簡(jiǎn)單,別名模板只將 scalar<T> 展開(kāi)為 T,限制條件獨(dú)立出來(lái)作為一個(gè)模板參數(shù)用于排除不滿(mǎn)足條件的實(shí)例化類(lèi)型:

      代碼
      
      template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
      using scalar = T;
      template<typename T>
      void foo(scalar<T> val)
      {
        ...
      }
      foo(5);
      

      現(xiàn)在調(diào)用 foo(5) 時(shí),模板參數(shù)推導(dǎo)過(guò)程變?yōu)椋?/p>

      guided_deduce

      現(xiàn)在的邏輯變?yōu)椋魏?scalar<T> 都是 T,但是只有當(dāng) T 是算術(shù)類(lèi)型時(shí),scalar<T> 才有效。

      有人可能會(huì)問(wèn),為什么要編寫(xiě)一個(gè)這樣的模板,而不是直接限制 foo 的參數(shù)類(lèi)型:

      代碼
      
      template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
      void foo(T val)
      {
        ...
      }
      

      原因前面已經(jīng)說(shuō)過(guò),為了復(fù)用,scalar<T> 比那一長(zhǎng)串檢測(cè)更簡(jiǎn)潔。這也是 C++20 concept 的低級(jí)實(shí)現(xiàn)版本:

      代碼
      
      template<typename T>
      concept scalar = std::is_arithmetic_v<T>;
      template<scalar S>
      void foo(S val)
      {
        ...
      }
      

      其他考量

      我在另一篇筆記 限制模板實(shí)參類(lèi)型 中提到過(guò),使用靜態(tài)斷言,可在發(fā)生編譯錯(cuò)誤時(shí)提供可讀性更高的錯(cuò)誤提示,我們的這個(gè)例子恰好很符合這一情況:

      代碼
      
      template<typename T>
      struct arithmetic_guard
      {
        static_assert(std::is_arithmetic_v<T>, "instantiation requires arithmetic type");
      using type = T; } template<typename T, typename = typename arithmetic_guard<T>::type> using scalar = T;

      改造之后的 scalar 模板如果使用非算術(shù)類(lèi)型進(jìn)行實(shí)例化,就會(huì)在編譯時(shí)指出需要算術(shù)類(lèi)型。

      但是靜態(tài)斷言版本存在一個(gè)缺點(diǎn),就是和 SFINAE 不兼容,假如我們想使用 scalar 來(lái)編寫(xiě)一個(gè)這樣的模板:

      代碼
      
      template<typename T>
      auto selected_type_impl(int) -> decltype(std::declval<scalar<T>>(), 0.0);
      template<typename>
      int selected_type_impl(...);
      template<typename T>
      using selected_type = decltype(selected_type_impl<T>(0));
      

      如果 scalar 使用靜態(tài)斷言版本實(shí)現(xiàn),那么我們使用非算術(shù)類(lèi)型實(shí)例化 selected_type 時(shí),得到的不是一個(gè) int 類(lèi)型,而是編譯錯(cuò)誤。因?yàn)?SFINAE 的發(fā)生時(shí)機(jī)是在模板參數(shù)替換階段,將判斷從模板參數(shù)列表移入 static_assert 內(nèi)的后果就是任何 selected_type_impl 版本都會(huì)進(jìn)行實(shí)例化而不會(huì)被靜默移除,不符合條件的版本將在這一過(guò)程中拋出錯(cuò)誤。在實(shí)際編碼時(shí),可根據(jù)具體需求選擇合適的實(shí)現(xiàn)版本。

      一些想法

      C++ 語(yǔ)言在不斷嘗試簡(jiǎn)化模板元編程,C++26 會(huì)將靜態(tài)反射加入語(yǔ)言標(biāo)準(zhǔn),屆時(shí)程序的元信息可以直接獲取,而不是通過(guò)編寫(xiě)七彎八繞的模板來(lái)“套出”這些信息。

      但是復(fù)雜性不是模板元編程的缺陷,相反它能容納更多的可能性。優(yōu)秀的模板庫(kù)在缺乏編譯器支持的年代解決問(wèn)題的思路,很多令人拍案叫絕,成為經(jīng)典用法甚至推動(dòng)了語(yǔ)言標(biāo)準(zhǔn)的發(fā)展,為更高階的功能實(shí)現(xiàn)奠定基礎(chǔ)。

      研究并掌握這些復(fù)雜巧妙的實(shí)現(xiàn),運(yùn)用它們?cè)诂F(xiàn)實(shí)問(wèn)題之前逢山開(kāi)路遇水搭橋,不斷磨煉我們的思維,而不是對(duì)它們望而卻步。這樣在面對(duì)語(yǔ)言標(biāo)準(zhǔn)提供的新特性時(shí),我們才能敏銳察覺(jué)到它們的設(shè)計(jì)意圖,善于恰當(dāng)?shù)丶右赃\(yùn)用,而不是淺嘗輒止。

      問(wèn)題總結(jié)

      思緒飄忽說(shuō)了一些廢話(huà),回到代碼的問(wèn)題上,其實(shí)是自己對(duì)模板推導(dǎo)規(guī)則了解太淺,臆造出一個(gè)看似可行的實(shí)現(xiàn),一廂情愿地認(rèn)為編譯器會(huì)如此工作。以后還須多多看書(shū)和實(shí)踐,增加知識(shí)儲(chǔ)備。

      AI 有什么表現(xiàn)

      出于好奇,我拿這個(gè)問(wèn)題問(wèn) AI,看它們能否分析出來(lái),以下是問(wèn)題的結(jié)果。

      DeepSeek 的推理能力比較不錯(cuò),而且完全免費(fèi),使用它的深度思考模式提問(wèn),得到的結(jié)論是兩個(gè)調(diào)用都沒(méi)有問(wèn)題:

      deepseek

      微軟 Edge 自帶的免費(fèi)版 Copilot 作為日常代碼問(wèn)題咨詢(xún)以及閑聊對(duì)象很方便,ThinkDeeper 模式下,它很確定兩種都能正確編譯:

      bing

      Claude 生成代碼的能力非常強(qiáng),廣受好評(píng),使用它的 concise 模式(普通模式下詢(xún)問(wèn)代碼問(wèn)題,Claude 會(huì)在展開(kāi)頁(yè)分析代碼,難以完整截圖)回答這個(gè)問(wèn)題,它認(rèn)為兩個(gè)都不能通過(guò)編譯:

      claude

      號(hào)稱(chēng)地表最強(qiáng)的 Grok 經(jīng)過(guò)仔細(xì)分析后,也沒(méi)能得出正確的結(jié)論:

      grok

      這四個(gè)都沒(méi)有完全分析正確,這讓我有一點(diǎn)意外。清除聊天上下文后拿相同的問(wèn)題再提問(wèn),它們每次幾乎都會(huì)給出不一樣的結(jié)論,偶爾能正確地預(yù)測(cè)。持續(xù)聊天并引導(dǎo)它們分析問(wèn)題,它們中很少能夠準(zhǔn)確說(shuō)出問(wèn)題出在非推導(dǎo)上下文這個(gè)點(diǎn)上。

      這很難讓人完全放心的將代碼完全交由 AI 編寫(xiě),目前來(lái)看,使用它們咨詢(xún)一些編碼問(wèn)題,從中得到啟發(fā)并親自確認(rèn)或者深入研究才是比較穩(wěn)妥的做法。

      posted @ 2025-08-04 18:33  saltymilk  閱讀(274)  評(píng)論(6)    收藏  舉報(bào)
      主站蜘蛛池模板: 丰台区| 亚洲香蕉网久久综合影视| av中文字幕国产精品| 亚洲高清WWW色好看美女| 5555国产在线观看| 91国产自拍一区二区三区| 无码熟妇人妻av在线电影| 久久国产精品二国产人妻| 安仁县| 日本一区二区三区专线| 国产亚洲999精品AA片在线爽| 成人亚洲狠狠一二三四区| 高清偷拍一区二区三区| 欧美成人免费一区二区三区视频| 国产日韩乱码精品一区二区| 2021亚洲国产精品无码| 人妻中文字幕精品一页| 91久久夜色精品国产网站| 亚洲精品成人片在线观看精品字幕 | 美女黄网站18禁免费看| 国产伦一区二区三区视频| 日韩av一区二区精品不卡| 一区二区三区精品自拍视频| 国产精品自在线拍国产手机版| 少妇粗大进出白浆嘿嘿视频| 青青狠狠噜天天噜日日噜| 国产喷水1区2区3区咪咪爱AV| 熟妇人妻中文a∨无码| 国产成人精品无码播放| 人成午夜免费大片| 国产精品毛片在线完整版| 在线观看无码av免费不卡网站| 亚洲 中文 欧美 日韩 在线| 花垣县| 日韩av片无码一区二区不卡| 国产福利永久在线视频无毒不卡| 国产a网站| 国产成人无码A区在线观看视频| 申扎县| 亚洲av二区三区在线| 中文日产乱幕九区无线码|