Home People Research Blog Courses Links Search Download
NCHC

Blog

Blog 最新文章

  1. Visual Studio 的遠端偵錯:Windows
    2021/03/24 14:45
  2. Visual Studio 2019 16.9 支援使用 OpenMP LLVM
    2021/03/03 13:52
  3. Valve 推出完全支援 OpenXR 的 SteamVR 1.16.8
    2021/02/25 09:43

Blog 最新回應

  1. 加入斜體文字...
    2021/02/07 21:06
  2. 加入斜體文字...
    2021/02/07 21:06
  3. 加入斜體文字...
    2021/02/07 21:06

Keyword 關鍵字

C++14 ASUS Xtion MR C++ javascript xml 3D立體 OpenCL OpenCV Pandas Docker HTC Vive OpenNI2 git C++11 VR C++17 Java HTC Vive Pro 3d print OpenXR NiTE2 Valve Index Vulkan WebGL HTC Vive Focus 開放資料 Pandas OpenNI CubeX Boost HoloLens 2 C++14 OpenMP C++20 VR 資料視覺化 PHP Kinect Docker Windows MR CUDA GitLab Oculus Quest 2 svn Python Python 資訊地圖 Oculus Rift S iFlyover OpenGL 開放資料 Qt Oculus OpenVR

類別:技術相關 » 技術研究
文章發表|我要回應|RSS訂閱

template 型別的需求描述:C++20 concepts

Template 是 C++ 一個泛型的重要功能。透過 template 可以讓開發者只要寫一次,就可以針對不同的型別的資料,來做處理。

他雖然用起來很方便,但是大部分情況下都缺少對於需求型別的描述,所以如果沒用好寫錯了,就很有可能因為使用了不符合需求的型別,而導致無法正確編譯;而這個時候,編譯器給個錯誤訊息往往也會過於雜亂、讓開發者難以理解、也難以找到真正的問題點。

比如說下面這個簡單的範例程式:

struct A
{
  int value;
};
 
template<typename T>
auto get_value(const T& a)
{
  auto v = a.value;
  // do some thing
  return v;
}
 
int main()
{
  A a{ 10 };
  auto x = get_value(a);
}

這個程式裡面,get_value() 這個函式會去取得傳入參數 a 的 value 這個成員變數的值,然後經過處理後、回傳結果的值。

上面的程式由於 A 這個結構有定義 value,所以使用上並沒有問題,可以正確編譯、也可以正確執行。

但是如果其他想要使用 get_value() 這個函式的人不知道使用的型別需要有 value 這個成員的話,那會怎樣呢?比如說,把傳入參數的型別從 A 變成 int 的話,main() 會變成:

int main()
{
  int a{ 10 };
  auto x = get_value(a);
}

這時候,就會出現錯誤而無法完成編譯了~

而在 Visual Studio 中的編譯錯誤,訊息會如下:

1>1>concept.cpp
1>concept.cpp(9,1): error C2228: '.value' 的左邊必須有類別/結構/等位
1>concept.cpp(9,1): message : 類型為 'const T'
1>concept.cpp(9,1): message :         with
1>concept.cpp(9,1): message :         [
1>concept.cpp(9,1): message :             T=int
1>concept.cpp(9,1): message :         ]
1>concept.cpp(17): message : 請參考正在進行編譯的函式 樣板 具現化 'auto get_value(const T &)'
1>concept.cpp(17): message :         with
1>concept.cpp(17): message :         [
1>concept.cpp(17): message :             T=int
1>concept.cpp(17): message :         ]
1>concept.cpp(11,1): error C3536: 'v': 無法在初始化之前使用
1>專案 "Concept.vcxproj" 建置完成 -- 失敗。

而彙整的錯誤報告有兩個,分別是:

  • 錯誤 C2228:'.value' 的左邊必須有類別/結構/等位 S:CppTestConceptconcept.cpp 9
  • 錯誤 C3536:'v': 無法在初始化之前使用 S:CppTestConceptconcept.cpp 11

如果點選上面的錯誤、Visual Studio 所跳到的程式碼,都會在 get_value() 內,讓人較難以判斷;而如果程式的 template 用得更複雜的話,在不熟悉這類的錯誤、同時也難以認真去讀編譯器輸出的訊息(message)的狀況下,那要判斷錯誤原因的狀況,是相當困難的…


為了解決這樣的問題,C++20 引進了所謂的「concepts」,用來針對 template 的型別,做額外的描述。

這部分的介紹可以參考《Constraints and concepts》或《C++20: Concepts, the Details》。而 Visual Studio 2019 在 16.3 後,也已經有部分支援 concepts(參考《C++20 Concepts Are Here in Visual Studio 2019 version 16.3》)了~這篇的程式測試,基本上都是在 Visual Studio 上測試的。

不過老實說由於這東西應該還算是在很早期的階段,所以相關介紹感覺還不算完整,Heresy 自己邊看邊摸,也不知道到底是不是完全正確;如果有錯的話,也麻煩指正一下了。

總之,如果要使用 concept 的話,上面的程式碼大致上就會修改成類似下面的樣子:

template<typename T>
concept have_value = requires(T a) {
  a.value;
};
 
template<typename T>
requires have_value<T>
auto get_value(const T& a)
{
  auto v = a.value;
  // do some thing
  return v;
}
 
int main()
{
  int b = 10;
  auto y = get_value(b);
}

其中,第一段就是在定義一個名為「have_value」的 concept。

template<typename T>
concept have_value = requires(T a) {
  a.value;
};

在 C++20 中,所謂的 concept 基本上就是一些「需求」(requirement)的集合,用來驗證型別是否符合需求用的。

而這個例子裡面,就是簡單地透過驗證 a.value; 這個式子是否合法、來確定型別 T 是否有 value 這個成員。

在定義好了 have_value 這個 concept 後,接下來則是幫本來的 get_value() 加上一行

requires have_value<T>

告訴編譯器以及使用者,這個函式的 template 型別 T 需要符合 have_value 這個 concept。

在這樣修改完成後,編譯的錯誤會變成是:

1>concept.cpp
1>concept.cpp(21,11): error C2672: 'get_value': 找不到相符的多載函式
1>concept.cpp(21,22): error C7602: 'get_value': 未滿足相關聯的限制式
1>concept.cpp(13): message : 請參閱 'get_value' 的宣告
1>專案 "Concept.vcxproj" 建置完成 -- 失敗。

基本上,錯誤算是更明確了!

而如果點選上面的錯誤的話,他也會跳到呼叫 main() 裡面呼叫 get_value() 的地方,而不會再直接進到 get_value() 內~

除了錯誤訊息更明確外,由於在 get_value() 宣告就明確地寫了它需要符合的條件,對於要使用它的開發者,也會更容易地去判斷自己要使用的型別,是否符合函式的設計了~


在 template 函式要使用,其實總共有三種寫法。第一種寫法(Requires Clause),就是像上面一樣,寫在 template<> 的後面的形式:

template<typename T>
requires have_value<T>
auto get_value(const T& a){ ... }

第二種方法(Trailing Requires Clause),則是把 requires 加在後面:

template<typename T>
auto get_value(const T& a) requires have_value<T>{ ... }

另外,甚至可以直接寫成下面更為精簡的形式(Constrained Template Parameters):

template<have_value T>
auto get_value(const T& a) { ... }

而除了針對 template 函式來做需求的限制外,他也可以用來做 template specialization。

比如說上面的程式就可以寫成:

struct A
{
  int value;
};
 
template<typename T>
concept have_value = requires(T a) {
  a.value;
};
 
template<have_value T> // have_vale
auto get_value(const T& a)
{
  auto v = a.value;
  // do some thing
  return v;
}
 
template<typename T> // generic
auto get_value(const T& a)
{
  auto v = a;
  // do some thing
  return v;
}
 
int main()
{
  A a{ 10 };
  auto x = get_value(a); // use have_vale
  
  int b = 10;
  auto y = get_value(b); // use generic
}

透過這樣撰寫兩個不同的 get_value(),就可以讓編譯器去判斷呼叫的時候要去用哪的版本了。在這個狀況下,只要所傳入的參數型別符合 have_value 這個 concept 的話,他就會去使用第一個版本,否則就會去使用第二個一般性的版本。

所以透過 concept 來做 template specialization,其實也算是滿實用的~


而除了用在 template function 上,cooncept 也能用在 template class 上。例如:

template<typename T>
requires have_value<T>
class CValue {};

或是

template<have_value T>
class CValue {};

此外,他也可以用在 template class 的 member funtion;例如:

template<typename T>
class CValue
{
public:
  T  m_val;
 
  int get_value() const
  {
    return m_val;
  }
 
  int get_value() const requires have_value<T>
  {
    return m_val.value;
  }
};

以上面的例子來說,雖然在 CValue 這個類別裡面同時有宣告兩個 get_value() 的成員函式,但是由於條件(requires)不同,所以其實並不會同時存在,所以並不會出現重複定義的狀況。

而在使用時,編譯器也會根據型別是否符合 have_value 這個 concept,來產生不同的版本。


這篇大概就先記錄這些,下一篇再來整理一下怎麼定義一個 concept 吧~


主要參考:

  • C++20: Concepts, the Details
  • Constraints and concepts
張貼者:heresy於2019/12/04 15:10 下午有0則回應,瀏覽次數:385次

-- TOP --

我要回應
* 身份  訪客 (暱稱:)
 本篇文章作者 (帳號:密碼:)
* 內容      
很高興 悲傷 震驚 疑惑 大笑 發瘋 傷心
* 留言密碼 (請輸入下方圖片中去除前、後位數的數字,共五碼。)
說明 1. * 表示必填欄位。
2. 不支援HTML Tag。
   

-- TOP --

© Visualization and Interactive Media Laboratory of NCHC, 2007 - 2021, All Rights Reserved. Contact E-mail