C++ コア ガイドライン:テンプレート インターフェイス

この投稿は、C++ コア ガイドラインによるテンプレート インターフェースに関するものです:「... 重要な概念」。テンプレート インターフェースは「ユーザーと実装者の間の契約であり、慎重に設計する必要がある」ためです。

今日のルールは次のとおりです。

  • T.41:テンプレートの概念に不可欠なプロパティのみを要求する
  • T.42:テンプレート エイリアスを使用して表記を簡素化し、実装の詳細を非表示にする
  • T.43:using を優先 typedef以上 エイリアスの定義用
  • T.44:関数テンプレートを使用して、クラス テンプレートの引数の型を推測します (可能な場合)

最初のルール T.41 から始めましょう:

T.41:テンプレートの概念では必須プロパティのみを要求する

本質的なプロパティだけを指定するとはどういう意味ですか?このガイドラインは、デバッグをサポートするソート アルゴリズムの例を示しています。

template<Sortable S>
 requires Streamable<S>
void sort(S& s) // sort sequence s
{
 if (debug) cerr << "enter sort( " << s << ")\n";
 // ...
 if (debug) cerr << "exit sort( " << s << ")\n";
}


ここで、重要でないプロパティを指定した場合の問題は何かという疑問が残ります。これは、コンセプトが実装に強く結び付いていることを意味します。その結果、実装を少し変更するだけで概念が変わる可能性があります。最終的に、インターフェースは非常に不安定になります。

T.42:テンプレート エイリアスを使用して表記を簡素化し、実装の詳細を隠す

C++11 以降、テンプレート エイリアスがあります。テンプレート エイリアスは、型のファミリを参照する名前です。それらを使用すると、コードが読みやすくなり、型の特徴を取り除くのに役立ちます。私の以前の投稿 C++ コア ガイドライン:概念の定義、2 番目は、型特性に関する詳細情報を提供します。

ガイドラインが読みやすさによって何を意味するか見てみましょう。最初の例では型特性を使用しています:

template<typename T>
void user(T& c)
{
 // ...
 typename container_traits<T>::value_type x; // bad, verbose
 // ...
}

これは、テンプレート エイリアスの対応するケースです。

template<typename T>
using Value_type = typename container_traits<T>::value_type;

void user2(T& c)
{
 // ...
 Value_type<T> x;
 // ...
}


可読性も、次のルールの根拠となります

T.43:using を優先 typedef 以上 エイリアスの定義用

読みやすさの観点から、typedef よりも優先する 2 つの引数があります。まず、使うときは使うことが先。第二に、使用感は auto と非常によく似ています。さらに、 using はテンプレート エイリアスに簡単に使用できます。

typedef int (*PFI)(int); // OK, but convoluted

using PFI2 = int (*)(int); // OK, preferred

template<typename T>
typedef int (*PFT)(T); // error (1) 

template<typename T>
using PFT2 = int (*)(T); // OK


最初の 2 行では、int を受け取り、int を返す関数 (PFI および PFI2) へのポインターを定義しています。最初のケースでは typedef が使用され、2 行目では using が使用されています。最後の 2 行では、型パラメーター T を受け取り、int を返す関数テンプレート (PFT2) を定義しています。行 (1) は無効です。

T.44:関数テンプレートを使用して、クラス テンプレートの引数の型を推測します (実行可能な場合)。 )

std::make_tuple や std::make_unique などの make_ 関数が多すぎる主な理由は、関数テンプレートが関数引数からテンプレート引数を推測できるためです。このプロセス中に、コンパイラは、最も外側の const/volatile 修飾子を削除し、C 配列と関数を C 配列の最初の要素へのポインターまたは関数へのポインターに減衰させるなど、いくつかの単純な変換を適用します。

この自動テンプレート引数推定は、プログラマーとしての私たちの生活をずっと楽にしてくれます。

入力する代わりに

std::tuple<int, double, std::string> myTuple = {2011, 20.11, "C++11"};

ファクトリ関数 std::make_tuple を使用します。

auto myTuple = std::make_tuple(2011, 20.11, "C++11");

悲しいことに、テンプレート型の自動推定は C++ で関数テンプレートでのみ使用できます。なんで?クラス テンプレートのコンストラクタは、特別な静的関数です。右! C++17 では、コンパイラはコンストラクタ引数からテンプレート引数を推測できます。 C++17 で myTuple を定義する方法は次のとおりです。

std::tuple myTuple = {2017, 20.17, "C++17"};

この C++17 機能の明らかな影響は、ほとんどの make_ 関数が C++17 で廃止されることです。

引数推定ガイドを含むクラス テンプレート引数推定の詳細を知りたい場合は、最新の C++ 機能 - Arne Mertz によるクラス テンプレート引数推定の投稿をお読みください。

C++ の教えやすさ

認めざるを得ませんが、私はこの C++17 機能が気に入っています。 C++ トレーナーとしての私の仕事は、この難しいことを説明することです。 C++ がより対称的になればなるほど、一般的なアイデアについて話すのがより簡単になります。これで、「テンプレートは、関数の引数からテンプレートの引数を自動的に推測できる」と言えます。以前は、これは関数テンプレートに対してのみ機能すると言わざるをえませんでした。

以下に簡単な例を示します:

// templateArgumentDeduction.cpp

#include <iostream>

template <typename T>
void showMe(const T& t){
 std::cout << t << std::endl;
}

template <typename T>
struct ShowMe{
 ShowMe(const T& t){
 std::cout << t << std::endl;
 }
};

int main(){
 
 std::cout << std::endl;
 
 showMe(5.5); // not showMe<double>(5.5);
 showMe(5); // not showMe<int>(5);
 
 ShowMe(5.5); // not ShowMe<double>(5.5);
 ShowMe(5); // not ShowMe<int>(5);
 
 std::cout << std::endl;
 
}

関数テンプレート showMe やクラス テンプレート ShowMe の使い方は同じように感じます。ユーザーの観点からは、テンプレートを使用していることを知りません。

現在の GCC 8.2 では、プログラムはコンパイルおよび実行されます。

より具体的には、GCC 7、Clang 5、および MSVC 19.14 以降、テンプレート引数の推論が機能するはずです。 cppreference.com には、コンパイラ サポートの詳細が記載されています。

次は?

レギュラー型、セミレギュラー型って知ってる?そうでない場合は、次のテンプレート インターフェースへの投稿が最適です。ルール T.46 は次のように述べています:「テンプレートの引数は、少なくとも Regular または SemiRegular である必要があります。」