私はよくテンプレートの基本を教えます。テンプレートは特別です。したがって、私は驚きを引き起こす多くの誤解に遭遇します。それらのいくつかを次に示します。
私の最初の誤解は、おそらく多くの C++ 開発者にとって明らかですが、すべての C++ 開発者にとってそうではありません。
関連型のテンプレートは関連していません
そもそも関連型って何?これは、暗黙的に変換できる型を表す非公式の用語です。ここが出発点です。
// genericAssignment.cpp #include <vector> template <typename T, int N> // (1) struct Point{ Point(std::initializer_list<T> initList): coord(initList){} std::vector<T> coord; }; int main(){ Point<int, 3> point1{1, 2, 3}; Point<int, 3> point2{4, 5, 6}; point1 = point2; // (2) auto doubleValue = 2.2; auto intValue = 2; doubleValue = intValue; // (3) Point<double, 3> point3{1.1, 2.2, 3.3}; point3 = point2; // (4) }
クラス テンプレート Point は、n 次元空間内の点を表します。座標のタイプと寸法を調整できます (1 行目)。座標は std::vector
今、誤解が始まります。 int を double に割り当てることができます (3 行目)。したがって、int の Point を double の Point に割り当てることができるはずです。 C++ コンパイラは、行 4 について非常に具体的です。両方のクラス テンプレートは関連付けられておらず、割り当てることができません。それらは異なるタイプです。
エラー メッセージは、最初のヒントを示します。 Point
// genericAssignment2.cpp #include <algorithm> #include <iostream> #include <string> #include <vector> template <typename T, int N> struct Point{ Point(std::initializer_list<T> initList): coord(initList){} template <typename T2> Point<T, N>& operator=(const Point<T2, N>& point){ // (1) static_assert(std::is_convertible<T2, T>::value, "Cannot convert source type to destination type!"); coord.clear(); coord.insert(coord.begin(), point.coord.begin(), point.coord.end()); return *this; } std::vector<T> coord; }; int main(){ Point<double, 3> point1{1.1, 2.2, 3.3}; Point<int, 3> point2{1, 2, 3}; Point<int, 2> point3{1, 2}; Point<std::string, 3> point4{"Only", "a", "test"}; point1 = point2; // (3) // point2 = point3; // (4) // point2 = point4; // (5) }
(1) 行により、(3) 行のコピー割り当てが機能します。クラステンプレートを詳しく見てみましょう ポイント:
- Point
&operator=(const Point &point):割り当てられた Point は Point 型であり、同じ次元を持つ Point のみを受け入れますが、タイプは異なる場合があります:Point . - static_assert(std::is_convertible
::value, "Cannot convert source type to destination type!"):この式は、型特性ライブラリの関数 std::is_convertible を使用してチェックします。 T2 を T に変換できる場合
行 (4) と (5) を使用すると、コンパイルが失敗します:
行 (3) は、両方の点の次元が異なるため、エラーになります。行 (4) は代入演算子で static_assert をトリガーします。これは、std::string が int に変換できないためです。
次の誤解はもっと驚くべき可能性を秘めていると思います.
クラス テンプレートから継承されたメソッド自体は利用できません
簡単に始めましょう。
// inheritance.cpp #include <iostream> class Base{ public: void func(){ // (1) std::cout << "func" << std::endl; } }; class Derived: public Base{ public: void callBase(){ func(); // (2) } }; int main(){ std::cout << std::endl; Derived derived; derived.callBase(); std::cout << std::endl; }
クラス Base と Derived を実装しました。 Derived は Base から派生した public であるため、そのメソッド callBase (2 行目) クラス Base のメソッド func で使用できます。わかりました、プログラムの出力に追加するものは何もありません.
Base をクラス テンプレートにすることで、動作が完全に変わります。
// templateInheritance.cpp #include <iostream> template <typename T> class Base{ public: void func(){ // (1) std::cout << "func" << std::endl; } }; template <typename T> class Derived: public Base<T>{ public: void callBase(){ func(); // (2) } }; int main(){ std::cout << std::endl; Derived<int> derived; derived.callBase(); std::cout << std::endl; }
コンパイル エラーに驚かれるかもしれません。
エラーメッセージの「テンプレートパラメーターに依存する 'func' への引数はないため、'func' の宣言を使用できる必要があります」という行が最初のヒントです。 func は、その名前がテンプレート パラメーター T に依存しないため、いわゆる非依存の名前です。その結果、コンパイラは from T 依存基底クラス Base
名前ルックアップを依存基底クラスに拡張するには、3 つの回避策があります。次の例では、3 つすべてを使用しています。
// templateInheritance2.cpp #include <iostream> template <typename T> class Base{ public: void func1() const { std::cout << "func1()" << std::endl; } void func2() const { std::cout << "func2()" << std::endl; } void func3() const { std::cout << "func3()" << std::endl; } }; template <typename T> class Derived: public Base<T>{ public: using Base<T>::func2; // (2) void callAllBaseFunctions(){ this->func1(); // (1) func2(); // (2) Base<T>::func3(); // (3) } }; int main(){ std::cout << std::endl; Derived<int> derived; derived.callAllBaseFunctions(); std::cout << std::endl; }
- 名前を依存させる :1 行目の this->func1 の呼び出しは、これが暗黙的に依存しているため、依存しています。この場合、名前検索ではすべての基本クラスが考慮されます。
- 名前を現在のスコープに導入: Base
::func2 を使用する式 (2 行目) は、func2 を現在のスコープに導入します。 - 名前を完全修飾して呼ぶ :func3 を完全修飾 (3 行目) で呼び出すと、仮想ディスパッチが中断され、新たな驚きが生じる可能性があります。
最後に、これがプログラムの出力です。
次は?
従属名については、次の投稿でもっと書きたいことがあります。場合によっては、依存する名前を typename または template で明確にする必要があります。これを初めて見た場合、おそらく私と同じくらい驚くでしょう。