C++ テンプレートとパラメータ多態性の根本的な違い

WARNING

この記事ChatGPTによって中国語から翻訳されたものでいくつかのりがまれているかもしれません。不正確部分があればご了承ください

C++ テンプレートパラメータ多態性

テンプレート(template) C++ 言語特性、以下のようなものです

1template <class T>
2T id(T x) {
3    return x;
4}

ここでの T 任意すことができます。多くの人々はこれがパラメータ多態性(parametric polymorphism)ていることにづきさらにはテンプレートメカニズムパラメータ多態性一種直接見なすもいます。例えばこのpptやこのwebページ

しかしこのブログ記事ではC++ テンプレート言語特性であるかどうかはとしてそれがパラメータ多態性とは根本的なること説明します

パラメータ多態性とは

パラメータ多態性歴史、講義ノート(Fundamental Concepts in Programming Languages)必要がありますこの講義ノート CPL 言語説明していますが、実際には以下部分 LISP MAP 関数説明しているはずです

パラメータ多態性はより規則的、例げて説明することができますf α 引数、結果 β 関数つまりf α β くことができますでありL がすべての要素 α リストつまりL α listであるとしますf 順番 L メンバー適用、結果リスト関数、つまり Map 想像することができますしたがってMap[f,L] β list 生成します。私たちは Map f 適切関数であるすべてのリスト動作することをんでいますしたがってMap 多態性必要がありますしかしその多態性単純パラメータであり、書

(αβ,α list)β list (α → β , α\ \text{list}) → β \ \text{list}

1975ML言語このような多態性システムレベル実現しましたML言語では、関数型変数めることができます。例えば、以下id関数

1let id (x:'a) = x

id関数'a -> 'aここでの'a型変数任意マッチすることができますそのため

1id 1
2id "ssdfs"
3id 1.23

はすべて合法ですML言語システムSystem F制約拡張えたものとることができますSystem Fλ拡張ることができSystem Fではλ各束縛変数には指定する必要がありこのΛΛ大文字λ導入されます

具体的には、最基本的λ λx.xλx.x System Fでは以下のようにくべきです

Λt.λ(x:t).x Λt.λ (x:t).x

そしてこの

t.tt ∀t.t → t

となりますこのきな ΛΛ 導入された型変数、使用時明示的applyさな λλ apply同様必要ですSystem FではλのようにλλΛΛ以外のものは存在せず、一時的整数型えば1:Int1: \text{Int}追加すると、以下のような評価例提供できます

((Λt.λ(x:t).x)Int)1(λ(x:Int).x)11 \begin{aligned} ((Λ t. λ (x:t). x) \text{Int})1 & \Rightarrow \\ (λ (x:\text{Int}). x) 1 & \Rightarrow 1 \end{aligned}

Hindey-Milner型推論アルゴリズム存在によりML言語ではこのような Int\text{Int} 手動提供する必要はありません。関数全称量子形式自動的推論されます。推論うためにSystem F合法ML構築できない場合がありますがSystem F型規則理解することは、今後議論いに役立ちます

System F型規則以下4つの規則けられます

  1. Γ,(x:t1)y:t2Γ(λ(x:t1).y):t1t2 {\frac{Γ , (x:t_1) ⊢ y:t_2}{Γ ⊢ (λ(x:t_1).y):t_1 → t_2}}_{}
  2. Γ,A:typex:t1Γ(ΛA.x):A.t1 \frac{Γ , A:\bold{type} ⊢ x:t_1 }{ Γ ⊢ (Λ A.x): ∀ A. t_1}
  3. Γx:(t1t2),y:t1Γ(x y):t2 \frac{Γ ⊢ x:(t_1 → t_2), y:t_1}{Γ ⊢ (x\ y): t_2}
  4. Γx:(A.t1)    (B:type)Γ(x B):[B/A]t1 \frac{Γ ⊢ x:(∀A.t_1)\ \ \ \ (B:\bold{type})}{ Γ ⊢ (x\ B):[B/A]t_1}

ルール2.からわかるようにここでの AA 任意この ΛΛ 条件 AA がどのようなであっても xx t1t₁ であると推測できることですこれによりルール4.ではBB 任意です。証明なしでのことがられます

命題1. もし (ΛA.x)(ΛA.x) システムFじたであるなら、任意 tt して((ΛA.x) t)((ΛA.x)\ t) システムFじたである

命題1.パラメータ多態性実装したと主張する任意システムたすべき命題ですML言語ではこれは'a -> bここでのb引用符がなく、任意可能型、例えば'a、結果として'a -> 'aになる関数fして、型t任意合法MLxしてf x[t/'a]bであるという表現されます

えば、以前id関数ではid 10[int/'a]'aつまりintです

実際、パラメータ多態性はより条件、つまりパラメトリシティたしますがこれは比較的複雑なのでここではその意味説明しません

C++テンプレート命題1.たしますか

えはらかにたしませんこれがC++テンプレートパラメータ多態性でないつの理由です

えばid関数のようにくことができます

1template <class T>
2T id(T x) {
3    return 10;
4}

もしいていなければこれがエラーにならないことにくかもしれません実際、どのようにいてもエラーになりません

1template <class T>
2int id(T x) {
3    return x;
4}

実際エラーになるのはid関数使用するときです

1auto x = id("sdfsdf");
2
3------------------------
4.\test.cpp:5:10: error: cannot initialize return object of type 'const char *'
5      with an rvalue of type 'int'
6  return 10;
7         ^~
8.\test.cpp:9:14: note: in instantiation of function template specialization       
9      'id<const char *>' requested here
10    auto y = id("s");
11             ^
121 error generated.

これは無情真実げていますC++ コンパイラテンプレート関数チェックわず特殊化にのみチェックいますこれにより、命題1.たずTxTである場合id関数本体つまりreturn xTであると推測できる保証がないからです

ocamlジェネリックパラメータ無視します

1# let id (x: 'a) : int = x ;;
2val id : int -> int = <fun>
1# let id (x: 'a) : 'a = 10 ;;
2val id : int -> int = <fun>

Haskellエラー報告します

1Prelude> :{
2Prelude| id :: a -> a
3Prelude| id x = 10
4Prelude| :}
5
6<interactive>:6:8: error:
7No instance for (Num a) arising from the literal108      Possible fix:
9        add (Num a) to the context of
10          the type signature for:
11            id :: forall a. a -> a
12In the expression: 10
13      In an equation forid: id x = 10
1Prelude> :{
2Prelude| id :: a -> Int
3Prelude| id x = x
4Prelude| :}
5
6<interactive>:12:8: error:
7Couldn't match expected typeIntwith actual typea8ais a rigid type variable bound by
9        the type signature for:
10          id :: forall a. a -> Int
11        at <interactive>:11:1-14
12In the expression: x
13      In an equation forid: id x = x
14Relevant bindings include
15        x :: a (bound at <interactive>:12:4)
16        id :: a -> Int (bound at <interactive>:12:1)

てのC++パラメータ多相性ではなくそのテンプレートシステムML系言語ジェネリックシステムとはなるレベルにあります

クラス問題影響しますか

Haskellクラス「使用時エラー報告されるという現象こるのではないかともいるかもしれません。例えば

1max :: Ord p => p -> p -> p
2max x y = if y > x then y else x
3
4data X = I | J
5
6max I J -- 这里报错
7-- error:
8--    • No instance for (Ord X) arising from a use of ‘max’
9--    • In the expression: max I J
10--      In an equation for ‘it’: it = max I J

このエラー、簡単えばカスタムXOrdクラス実装しておらずmaxシグネチャクラスOrd必要とするためエラー発生しますこれはC++テンプレートとはなります。根本的いはmax I Jチェックにはmax :: Ord p => p -> p -> pという情報だけで十分でありmax関数本体であるかを必要はなくましてやこの関数「特化」する必要はありませんこれはシステムでの推論です

真偽ジェネリックパフォーマンス

Java言語をあまりんでいませんがJava言語ジェネリックシステム一般的状況下命題1たしておりそれはパラメータ多相性ですこれはJavaジェネリック前身であるGeneric JavaGJPhilip Wadlerなどのプログラミング言語専門家によって設計されたからです

Javaジェネリック型消去採用しており、「偽ジェネリックであるともいますがこれはらかにC++テンプレート設計影響けています。実際にはパラメータ多相性対応するパラメータジェネリック資格があるのはらかですC++「偽ジェネリックです

C++テンプレート設計モジュールシステム困難問題にしてしまいました2021年現在、99.9%C++プログラム依然として古代ヘッダーファイルこれは完全ゴミ使ってモジュールシステム模倣しています

パフォーマンスについては、「特化」という概念プログラミング言語において2つのより強力概念えることができます

  • 部分評価partial evaluation
  • インラインinlining

特化部分評価一例なすことができますF#バイトコードコンパイルしてC#すことができsharplabでは

1let select x y = x
2
3let select2 (x:int) (y:int) = select x y

このようなのようになります

1using System.Reflection;
2using Microsoft.FSharp.Core;
3
4[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
5[assembly: AssemblyVersion("0.0.0.0")]
6[CompilationMapping(SourceConstructFlags.Module)]
7public static class @_
8{
9    [CompilationArgumentCounts(new int[] {
10        1,
11        1
12    })]
13    public static a select<a, b>(a x, b y)
14    {
15        return x;
16    }
17
18    [CompilationArgumentCounts(new int[] {
19        1,
20        1
21    })]
22    public static int select2(int x, int y)
23    {
24        return x;
25    }
26}
27namespace <StartupCode$_>
28{
29    internal static class $_
30    {
31    }
32}

select2select「特化」ではないのでしょうかしたがってC++テンプレートパフォーマンスという主張明確ではありません

コンセプト問題解決できますか

パラメータ多態性があるかどうかは、学術的問題のようにえますパラメータ多態性がないと、主パラメトリックparametric typeつまり使用する潜在的エラー発生パラメトリシティたないと、一部定理えばmap f . map g = map (f . g)ちません

以前にもれたようにHaskellにもエラー発生する問題があります。他ML言語ではF#のようなオブジェクトシステム拡張けば、基本的エラー発生しませんがよりくのパラメータ必要があったりモジュールmoduleシステム明示的使用する必要がありますF#List.sortWith(('a -> 'a -> int) -> 'a list -> 'a list)、任意'aしてエラー発生しませんがcomparer明示的必要があります

したがってパラメータ多態性がないということはそれほどきな問題ではないかもしれません

しかしC++についてえばパラメータ多態性がないことはテンプレートシステムエラーメッセージ非常いにくいというかなり深刻問題こします

えば、次のようなプログラム

1#include <iostream>
2#include <vector>
3#include <algorithm>
4
5class A {};
6
7int main() {
8    std::vector<A> v;
9    std::sort(v.begin(), v.end());
10}

200行、19kbエラー生成します

1In file included from .\test.cpp:3:
2C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\include\algorithm:7361:32: error: no matching function for call to object of type 'std::less<>'
3            if (_DEBUG_LT_PRED(_Pred, _Val, *_First)) { // found new earliest element, move to front
4...

この問題解決するためにC++20concept導入しましたconceptクラスJavaジェネリクス設計れており、以前Haskellmaxのようなプログラムくことができます

1#include <iostream>
2#include <algorithm>
3#include <concepts>
4
5template <class T> requires std::equality_comparable<T>
6bool max(T x, T y) {
7    if (x > y) {
8        return x;
9    } else {
10        return y;
11    }
12}

この以下のようにくと

1class A{};
2
3int main() {
4    max(A(), A());
5}

コンパイラエラー報告します

1source>: In function 'int main()':
2<source>:17:8: error: no matching function for call to 'max(A, A)'
3   17 |     max(A(), A());
4      |     ~~~^~~~~~~~~~
5<source>:6:6: note: candidate: 'template<class T>  requires  equality_comparable<T> bool max(T, T)'
6    6 | bool max(T x, T y) {
7      |      ^~~
8<source>:6:6: note:   template argument deduction/substitution failed:
9<source>:6:6: note: constraints not satisfied
10In file included from /opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/compare:39,
11                 from /opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/bits/stl_pair.h:65,
12                 from /opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/bits/stl_algobase.h:64,
13                 from /opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/bits/char_traits.h:39,
14                 from /opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/ios:40,
15                 from /opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/ostream:38,
16                 from /opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/iostream:39,
17                 from <source>:1:
18/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts: In substitution of 'template<class T>  requires  equality_comparable<T> bool max(T, T) [with T = A]':
19<source>:17:8:   required from here
20/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts:280:15:   required for the satisfaction of '__weakly_eq_cmp_with<_Tp, _Tp>' [with _Tp = A]
21/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts:290:13:   required for the satisfaction of 'equality_comparable<T>' [with T = A]
22/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts:281:4:   in requirements with 'std::remove_reference_t<_Tp>& __t', 'std::remove_reference_t<_Up>& __u' [with _Up = A; _Tp = A]
23/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts:282:17: note: the required expression '(__t == __u)' is invalid
24  282 |           { __t == __u } -> __boolean_testable;
25      |             ~~~~^~~~~~
26/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts:283:17: note: the required expression '(__t != __u)' is invalid
27  283 |           { __t != __u } -> __boolean_testable;
28      |             ~~~~^~~~~~
29/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts:284:17: note: the required expression '(__u == __t)' is invalid
30  284 |           { __u == __t } -> __boolean_testable;
31      |             ~~~~^~~~~~
32/opt/compiler-explorer/gcc-11.2.0/include/C++/11.2.0/concepts:285:17: note: the required expression '(__u != __t)' is invalid
33  285 |           { __u != __t } -> __boolean_testable;
34      |             ~~~~^~~~~~
35cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail
36Compiler returned: 1

ここではAstd::equality_comparable制約たしていないことが直接示されていますそれにもかかわらずノイズ非常いですさらに重要なのはソースコード調査したくないライブラリ関数しては、効果一般的であることです

1n file included from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/stl_algobase.h:71,
2                 from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/char_traits.h:39,
3                 from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/ios:40,
4                 from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/ostream:38,
5                 from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/iostream:39,
6                 from <source>:1:
7/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/predefined_ops.h: In instantiation of 'constexpr bool __gnu_cxx::__ops::_Iter_less_iter::operator()(_Iterator1, _Iterator2) const [with _Iterator1 = __gnu_cxx::__normal_iterator<A*, std::vector<A> >; _Iterator2 = __gnu_cxx::__normal_iterator<A*, std::vector<A> >]':
8/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/stl_algo.h:1826:14:   required from 'constexpr void std::__insertion_sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<A*, std::vector<A> >; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
9/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/stl_algo.h:1866:25:   required from 'constexpr void std::__final_insertion_sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<A*, std::vector<A> >; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
10/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/stl_algo.h:1957:31:   required from 'constexpr void std::__sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<A*, std::vector<A> >; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
11/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/stl_algo.h:4842:18:   required from 'constexpr void std::sort(_RAIter, _RAIter) [with _RAIter = __gnu_cxx::__normal_iterator<A*, std::vector<A> >]'
12<source>:10:14:   required from here
13/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/predefined_ops.h:45:23: error: no match for 'operator<' (operand types are 'A' and 'A')
14   45 |       { return *__it1 < *__it2; }
15      |                ~~~~~~~^~~~~~~~
16
17剩下还有80多行

公平えばC++テンプレート設計もいくつかの利点をもたらしています。理論的にはコンパイラには特化がもたらすパフォーマンス向上るための一般的方法がありますが、実際困難1によりC++テンプレートパフォーマンスかにいくつかの優位性っていますここでパフォーマンスとは実行時パフォーマンスでありコンパイルパフォーマンスについてはC++テンプレート非常いです

言語設計自体よりも、私がよりきでないのは、一部C++プログラマー無知その無知がもたらす傲慢態度です。一部人々JavaジェネリクスジェネリクスC++テンプレートのようなジェネリクス実装だけがジェネリクスであるとえています。私からこれらの人々にこの記事真剣むことをおめします