第323章 クラステンプレートの型推論ガイド( C++17 )

クラステンプレート型の推論は定義を緩くすると昔からありますた…

C++17 からはその推論機能が大幅に強化されました。

それでどういう機能かと言うと、テンプレートを指定しなくても、自動で推論してくれます。

別の言葉を使って説明すると、テンプレートの引数を指定もしていないのに、テンプレートの型を推論してくれるのがクラステンプレートの型推論です。

注記

といっても使い勝手が少しよくなったぐらいで C++17 の界隈でどれだけ需要があるかは疑わしいです。

なぜそう思うかと言うと最近使われているスクリプト言語に近い構文が使えるようになるという程度です。

コンパイラ依存もまだあるので、すぐに使えるというほどではないですし、導入されるのは数年後ぐらいの印象ですかね。

ではまず強化された型推論の例でも見てみましょうかね。

  1 #if __cplusplus < 201703L
  2 # pragma message "-std=c++17を指定してください"
  3 #endif
  4
  5 #if defined(__GNUC__) && (__GNUC__ < 7)
  6 # ifndef __clang__
  7 #   pragma message "gccのバージョンが古いです"
  8 # endif
  9 #elif defined(__clang__) && (__clang_major__ < 6)
 10 #  pragma message "clangのバージョンが古いです"
 11 #endif
 12
 13 #include <type_traits>
 14
 15 template<class T>
 16 struct A {
 17   A(T){}
 18   A(const A&){}
 19 };
 20
 21 #if __cplusplus >= 201703L
 22 template<class T>
 23 A(T) -> A<T>;
 24 #endif
 25
 26 int main()
 27 {
 28
 29 #if __cplusplus >= 201703L
 30   A a(10);
 31   A b = a;
 32   A c(a);
 33
 34 #else
 35   A<int> a(10);
 36   A<int> b = a;
 37   A<int> c(a);
 38 #endif
 39
 40   static_assert(std::is_same<decltype(a),A<int>>::value,"");
 41   static_assert(std::is_same<decltype(b),A<int>>::value,"");
 42   static_assert(std::is_same<decltype(c),A<int>>::value,"");
 43
 44   return 0;
 45 }

ちょっと C++17 と C++14 以前でも通すために無理をしていますが、重要なのはクラステンプレートのインスタンス化をするのにテンプレートパラメーターが不要な点です。

C++14 以前の構文では以下のようにテンプレート引数を指定していますね。

 15 template<class T>
 16 struct A {
 17   A(T){}
 18   A(const A&){}
 19 };

 //割愛

 35   A<int> a(10);
 36   A<int> b = a;
 37   A<int> c(a);

これは C++17 では以下のような構文に書き直すことができます。

 15 template<class T>
 16 struct A {
 17   A(T){}
 18   A(const A&){}
 19 };

 //割愛

 22 template<class T>
 23 A(T) -> A<T>;

 //割愛

 30   A a(10);
 31   A b = a;
 32   A c(a);

このようにテンプレート引数なしでインスタンス化できるため、文字を簡略できますね。

ただしこの構文を可能とするには推論ガイドという定義をしてやる必要があります。

それが以下の2行です。

 22 template<class T>
 23 A(T) -> A<T>;

これは T を引数とする A のコンストラクタ A(T) は A<T> として解釈ができるよ〜というコンパイラにヒントを与えています。

クラステンプレート型推論とはこんな風にガイドを与える必要がありますが、STLコンテナの中にはすでにガイドが提供されているものがあります。

例えば std::pair が良い例かと思います。

  1 #if __cplusplus < 201703L
  2 # pragma message "-std=c++17を指定してください"
  3 #endif
  4
  5 #include <utility>
  6 #include <iostream>
  7
  8 int main()
  9 {
 10 #if __cplusplus >= 201703L
 11   std::pair x{1,2};
 12 #else
 13   std::pair<int,int> x{1,2};
 14 #endif
 15   std::cout << std::get(1)(x) << '\n';
 16   return 0;
 17 }

この例ではテンプレート推論のガイドを定義してなくても推論が出来てますね。

 11   std::pair x{1,2};

C++14 以前の構文を考えるとかなり便利になったと言えるかもしれません。

 13   std::pair<int,int> x{1,2};

std::pair は他のSTLコンテナと使う事があるので、このように手軽に使える構文は悪くはないです。

他にも std::tuple も同じ使い方が可能となっています。

std::pair の他にもテンプレート引数での型が指定したものはいくつかありますが std::initializer_list がその最たるものだと思います。

  1 #if __cplusplus < 201703L
  2 # pragma message "-std=c++17を指定してください"
  3 #endif
  4
  5 #include <initializer_list>
  6 #include <iostream>
  7
  8 struct B { };
  9
 10 template<class T>
 11 struct A
 12 {
 13   A(T,B){}
 14   A(T,T){}
 15   A(const A&){}
 16   A(std::initializer_list<T>){}
 17 };
 18
 19 #if __cplusplus >= 201703L
 20 template<class T>
 21 A(T,B) -> A<T>;
 22
 23 template<class T>
 24 A(T,T) -> A<T>;
 25
 26 template<class T>
 27 A(std::initializer_list<T>) -> A<T>;
 28 #endif
 29
 30 int main() {
 31   A a(1,2);
 32   A b(1,B{});
 33   A c{1};
 34   A d(a);
 35 #if defined(___GNUC__) && (__GNUC__ >= 7)
 36 # ifndef __clang__
 37   std::initializer_list il{1,2,3};
 38   A e(il);
 39 # endif
 40 #elif defined(__clang__) && (__clang_major__ <= 6)
 41   std::initializer_list<int> il{1,2,3};
 42   A e(il);
 43 #endif
 44   return 0;
 45 }

gcc7.3 では以下の箇所は通ります。

 37   std::initializer_list il{1,2,3};
 38   A e(il);

ただこの構文を clang6.0 で試したところコンパイルに失敗してしまいました。

この例のように型推論はかなり発展途上らしくコンパイラ依存の部分が結構あるようです。

コンパイラ依存についてはもう一つ例をお見せしたいので次の項目をご覧ください。

Copyright 2017-2018, by Masaki Komatsu