偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

我和編譯器的深夜對(duì)話:關(guān)于 C++ SFINAE 的那些事

開(kāi)發(fā)
SFINAE(Substitution Failure Is Not An Error)是C++模板編程的核心技術(shù)之一,掌握了SFINAE,你就掌握了C++模板元編程的一把利器!

凌晨2點(diǎn),又是一個(gè)和Bug搏斗的夜晚...

我:編譯器大哥,又見(jiàn)面了...

編譯器:?jiǎn)眩±系?,又熬夜寫代碼?。窟@次又遇到什么問(wèn)題了?

我:別提了,我想寫個(gè)函數(shù)模板,能根據(jù)類型自動(dòng)選擇不同的實(shí)現(xiàn),結(jié)果一編譯就報(bào)錯(cuò)...

template<typename T>
void process(T value) {
    value.foo();  // 如果T有foo方法就調(diào)用
    // 如果沒(méi)有foo方法就做別的事
}

編譯器:停停停!你這樣寫我怎么編譯?如果傳進(jìn)來(lái)的類型沒(méi)有foo方法,我直接給你報(bào)錯(cuò)好吧!

我:那怎么辦啊?我就想要個(gè)"有就調(diào)用,沒(méi)有就算了"的效果...

編譯器:這就要用到SFINAE了,小老弟。

我:SFINAE?這是啥?

編譯器:全稱是"Substitution Failure Is Not An Error",翻譯過(guò)來(lái)就是"替換失敗不是錯(cuò)誤"。聽(tīng)起來(lái)很高大上對(duì)吧?

我:emmm...能說(shuō)人話嗎?

編譯器:?? 簡(jiǎn)單說(shuō)就是,當(dāng)我嘗試用某個(gè)類型去匹配模板時(shí),如果替換失敗了,我不會(huì)直接報(bào)錯(cuò),而是會(huì)去找其他可能的匹配。

我:還是不太懂...給個(gè)例子?

編譯器:行,看這個(gè):

#include <iostream>
#include <type_traits>

// 第一個(gè)版本:給有foo方法的類型用
template<typename T>
auto process(T& value, int) -> decltype(value.foo(), void()) {
    std::cout << "調(diào)用了foo方法\n";
    value.foo();
}

// 第二個(gè)版本:給沒(méi)有foo方法的類型用
template<typename T>
void process(T& value, long) {
    std::cout << "沒(méi)有 foo() 方法,做別的事\n";
    // 做別的事情
}

我:哇!這個(gè)decltype(value.foo(), void())是什么鬼?還有為什么一個(gè)用int一個(gè)用long?

編譯器:這就是SFINAE的精髓!我來(lái)解釋一下:

decltype(value.foo(), void()):這里用了逗號(hào)表達(dá)式,返回類型是void()

如果T有foo方法,這個(gè)表達(dá)式就能正常計(jì)算,函數(shù)匹配成功

如果T沒(méi)有foo方法,value.foo()就會(huì)失敗,但我不報(bào)錯(cuò)!我會(huì)去找其他重載

我:那int和long參數(shù)是干什么的?

編譯器:這是個(gè)小技巧!當(dāng)你調(diào)用process(obj, 0)時(shí):

  • 如果第一個(gè)版本(有int參數(shù))匹配成功,就選它(因?yàn)?匹配int更精確)
  • 如果第一個(gè)版本失敗了,就會(huì)選第二個(gè)版本(0也能轉(zhuǎn)換成long)
  • 這樣就實(shí)現(xiàn)了優(yōu)先級(jí)控制!

我:讓我試試!

struct HasFoo {
    void foo() { std::cout << "HasFoo::foo()\n"; }
};

struct NoFoo {
    void bar() { std::cout << "NoFoo::bar()\n"; }
};

int main() {
    HasFoo h;
    NoFoo n;
    
    process(h, 0);  // 會(huì)調(diào)用第一個(gè)版本
    process(n, 0);  // 會(huì)調(diào)用第二個(gè)版本
    return0;
}

編譯器:完美!注意調(diào)用時(shí)要傳入0作為第二個(gè)參數(shù),這樣就實(shí)現(xiàn)了根據(jù)類型特性自動(dòng)選擇不同實(shí)現(xiàn)。

我:哇塞!真的編譯通過(guò)了!但是這個(gè)寫法看起來(lái)好古老啊...

編譯器:哈哈,你說(shuō)得對(duì)?,F(xiàn)在有更現(xiàn)代的寫法,用std::enable_if:

#include <type_traits>

// 檢測(cè)是否有foo方法的工具
template<typename T>
class has_foo {
private:
    template<typename U>
    static auto test(int) -> decltype(std::declval<U>().foo(), std::true_type{});
    
    template<typename>
    static std::false_type test(...);
    
public:
    staticconstexprbool value = decltype(test<T>(0))::value;
};

// 有foo方法的版本
template<typename T>
std::enable_if_t<has_foo<T>::value> process(T value) {
    std::cout << "調(diào)用了foo方法\n";
    value.foo();
}

// 沒(méi)有foo方法的版本
template<typename T>
std::enable_if_t<!has_foo<T>::value> process(T value) {
    std::cout << "沒(méi)有foo方法,執(zhí)行默認(rèn)操作\n";
}

我:我去...這個(gè)has_foo是在干什么?

編譯器:這是個(gè)類型檢測(cè)器!它會(huì)在編譯期檢查類型T是否有foo方法:

  • test<U>(int)版本:如果U有foo方法,就返回std::true_type
  • test(...)版本:兜底版本,返回std::false_type
  • has_foo<T>::value就能得到布爾值結(jié)果

我:然后std::enable_if_t根據(jù)這個(gè)布爾值來(lái)啟用或禁用函數(shù)模板?

編譯器:聰明!std::enable_if_t<true>等于void,函數(shù)正常;std::enable_if_t<false>會(huì)導(dǎo)致替換失敗,觸發(fā)SFINAE,去找其他重載。

我:等等,還有更簡(jiǎn)單的寫法嗎?這個(gè)has_foo寫起來(lái)好復(fù)雜...

編譯器:C++17開(kāi)始有if constexpr,C++20有Concepts,但SFINAE的核心思想是一樣的。不過(guò)既然你問(wèn)了,我給你看個(gè)C++20的版本:

#include <concepts>

template<typename T>
concept HasFoo = requires(T t) {
    t.foo();
};

template<HasFoo T>
void process(T value) {
    std::cout << "調(diào)用了foo方法\n";
    value.foo();
}

template<typename T>
void process(T value) requires (!HasFoo<T>) {
    std::cout << "沒(méi)有foo方法,執(zhí)行默認(rèn)操作\n";
}

我:哇!這個(gè)Concepts看起來(lái)清爽多了!

編譯器:對(duì)吧!但是理解了SFINAE,你才能真正理解這些新特性的原理。SFINAE可是C++模板編程的基石!

我:那SFINAE還有其他用途嗎?

編譯器:多了去了!比如:

1. 檢測(cè)成員函數(shù)

// 檢測(cè)是否有begin()方法(判斷是否可迭代)
template<typename T>
auto is_iterable(T t) -> decltype(t.begin(), t.end(), std::true_type{});

std::false_type is_iterable(...);

2. 檢測(cè)操作符重載

// 檢測(cè)是否支持+操作
template<typename T, typename U>
auto can_add(T t, U u) -> decltype(t + u, std::true_type{});

std::false_type can_add(...);

3. 函數(shù)重載決議

// 針對(duì)不同數(shù)值類型的特化處理
template<typename T>
std::enable_if_t<std::is_integral_v<T>> process_number(T value) {
    std::cout << "處理整數(shù): " << value << "\n";
}

template<typename T>
std::enable_if_t<std::is_floating_point_v<T>> process_number(T value) {
    std::cout << "處理浮點(diǎn)數(shù): " << value << "\n";
}

我:原來(lái)SFINAE這么強(qiáng)大!但是為什么叫"替換失敗不是錯(cuò)誤"呢?

編譯器:因?yàn)樵跊](méi)有SFINAE之前,模板參數(shù)替換失敗就會(huì)直接編譯錯(cuò)誤。有了SFINAE,我會(huì)把失敗的候選從重載集合中刪除,繼續(xù)嘗試其他候選。只有所有候選都失敗了,才報(bào)錯(cuò)。

我:所以SFINAE讓模板編程更靈活了?

編譯器:沒(méi)錯(cuò)!它讓你能寫出真正泛型的代碼,根據(jù)類型特性自動(dòng)適配。這就是C++模板元編程的魅力所在!

我:等等,我想到一個(gè)問(wèn)題。如果兩個(gè)重載都能匹配怎么辦?

編譯器:好問(wèn)題!這時(shí)候就看重載決議的優(yōu)先級(jí)了:

  • 精確匹配 > 類型轉(zhuǎn)換
  • 非模板函數(shù) > 模板函數(shù)
  • 特化模板 > 通用模板
  • 參數(shù)匹配度高的 > 參數(shù)匹配度低的

我:明白了!最后一個(gè)問(wèn)題,SFINAE有什么坑需要注意的嗎?

編譯器:哈哈,當(dāng)然有!

1. 只在函數(shù)簽名中生效

template<typename T>
void bad_sfinae(T value) {
    // 這里的錯(cuò)誤不會(huì)觸發(fā)SFINAE,直接編譯錯(cuò)誤!
    static_assert(sizeof(T) > 100);
}

2. 嵌套模板的陷阱

template<typename T>
struct Wrapper {
    // 這里的SFINAE可能不會(huì)按你預(yù)期工作
    template<typename U = T>
    std::enable_if_t<std::is_integral_v<U>> func();
};

3. 調(diào)試?yán)щy

SFINAE錯(cuò)誤信息通常很難讀懂,建議多用static_assert輔助調(diào)試。

我:受教了!編譯器大哥,今天學(xué)到了很多!

編譯器:不客氣!記住,SFINAE不是魔法,它只是利用了C++的重載決議規(guī)則。多練習(xí),多思考,你很快就能成為模板編程高手!

我:好的!那我去試試用SFINAE重構(gòu)我的代碼了!

編譯器:去吧,少年!記?。捍a千萬(wàn)行,類型安全第一行。編譯不規(guī)范,同事兩行淚!

總結(jié)

SFINAE(Substitution Failure Is Not An Error)是C++模板編程的核心技術(shù)之一:

核心思想:模板參數(shù)替換失敗時(shí)不報(bào)錯(cuò),而是嘗試其他重載

常用場(chǎng)景:類型檢測(cè)、條件編譯、函數(shù)重載

現(xiàn)代替代:C++17的if constexpr、C++20的Concepts

注意事項(xiàng):只在函數(shù)簽名中生效,調(diào)試相對(duì)困難

掌握了SFINAE,你就掌握了C++模板元編程的一把利器!

編譯器:對(duì)了,小老弟,你這么愛(ài)學(xué)習(xí),肯定還想了解更多C++后臺(tái)開(kāi)發(fā)的干貨吧?

我:那必須的??!還有什么好的學(xué)習(xí)資源推薦嗎?

編譯器:我聽(tīng)說(shuō)有個(gè)叫"跟著小康學(xué)編程"的公眾號(hào)挺不錯(cuò)的,專門分享Linux C/C++后臺(tái)開(kāi)發(fā)的技術(shù),而且還有技術(shù)交流群可以加入。

我:真的嗎?那我得去關(guān)注一下!正好最近在學(xué)后臺(tái)開(kāi)發(fā),需要找個(gè)靠譜的地方交流學(xué)習(xí)。

編譯器:嗯,聽(tīng)說(shuō)群里的小伙伴都很活躍,經(jīng)常分享一些實(shí)戰(zhàn)經(jīng)驗(yàn)和踩坑心得。畢竟一個(gè)人悶頭學(xué)習(xí)容易走彎路,有個(gè)技術(shù)圈子還是很重要的!

我:說(shuō)得對(duì)!那我現(xiàn)在就去關(guān)注"跟著小康學(xué)編程",順便進(jìn)群交流去了~

編譯器:去吧去吧!記住,學(xué)習(xí)路上不孤單,大家一起進(jìn)步才是王道!??

責(zé)任編輯:趙寧寧 來(lái)源: 跟著小康學(xué)編程
相關(guān)推薦

2010-10-20 13:43:37

C++編譯器

2010-01-12 16:42:59

C++編譯器

2010-01-18 10:34:21

C++編譯器

2010-01-21 09:11:38

C++編譯器

2010-01-18 10:28:15

C++編譯器

2010-01-27 14:48:55

優(yōu)秀C++編譯器

2014-03-03 10:00:53

編譯器集成開(kāi)發(fā)環(huán)境

2010-01-08 16:00:46

C++編譯器

2010-01-14 15:29:44

C++編譯器

2010-02-03 13:14:03

C++編譯器命令

2013-03-18 09:42:47

C++C++ 11

2014-03-06 09:18:48

C++CIDE

2023-12-07 19:19:21

C++模板代碼

2015-03-23 10:04:43

c++編譯器c++實(shí)現(xiàn)原理總結(jié)

2023-11-15 17:58:58

C++代碼

2021-03-07 16:31:35

Java編譯反編譯

2017-08-29 09:30:01

編譯器LLVM編程語(yǔ)言

2010-01-21 09:26:53

CC++編譯器

2010-01-27 13:53:40

強(qiáng)大的CC++編譯器

2015-08-14 09:49:25

u3dc#
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)