C++ 17 __has_include 前置處理表達式

__has_include 是 C++ 17 加進前置處理器(Preprocessor)的新功能。我們能以 __has_include 表達式偵測某個標頭檔(Header File)是否存在。如果前置處理器能找到指定的標頭檔,__has_include 表達式的計算結果會是 1;反之,其計算結果會是 0。例如:

example.cpp:

#if __has_include("example.h")
#include "example.h"
#else
#error "cannot find example.h"
#endif

int main() {}

因為我們沒有建立 example.h,所以前置處理器在計算 #if 前置處理指令的時候,會將 __has_include("example.h") 表達式視為 0,進而觸發第 4 行的 #error

$ g++ -std=c++17 example.cpp
example.cpp:4:6: error: #error "cannot find example.h"
     #error "cannot find example.h"
      ^~~~~

如果我們建立 example.h

$ touch example.h

__has_include("example.h") 會被視為 1,因此同樣的 example.cpp 就能正常地編譯:

$ g++ -std=c++17 example.cpp

實際應用

一個常見的 __has_include 使用情境是同時支援不同來源的函式庫。舉例來說,如果你的專案要同時支援 C++ 14 與 C++ 17,而且你想要使用 C++ 17 的 std::optional,你可以使用 __has_include 測試 <optional> 標頭檔。如果找不到 <optional> 則以 <boost/optional/optional.hpp> 替代:

#if defined(__has_include) && __has_include(<optional>)
#include <optional>
using std::optional;
#else
#include <boost/optional/optional.hpp>
using boost::optional;
#endif

int main() {
  optional<int> x;
}

以上程式碼中,我們先以 defined(__has_include) 檢查前置處理器是否支援 __has_include 表達式。支援 C++ 17 的編譯器必須回傳 1。部分編譯器(例如:Clang)在 C++ 17 以前就以語言擴充(Language Extension)的形式支援 __has_include 表達式,所以我們是以 defined(__has_include) 檢查支援度。

接著,我們以 __has_include(<optional>) 偵測 <optional> 標頭檔。如果有找到,則直接使用標準函式庫的 std::optional,並以 using 宣告將 optional 引入視野(Scope)。如果沒有找到,則使用 Boost 函式庫的 boost::optional,它與標準函式庫 std::optional 相似,可以作為 std::optional 在 C++ 14 的替代品。

參考資料

Note

如果你喜歡這篇文章,請追蹤羅根學習筆記粉絲專頁。最新文章都會在粉絲專頁發佈通知。