変数テンプレートとは変数宣言をテンプレート宣言にできる機能だ。
template < typename T >
T variable { } ;
int main()
{
variable<int> = 42 ;
variable<double> = 1.0 ;
}
これだけではわからないだろうから、順を追って説明する。
C++ではクラスを宣言できる。
class X
{
int member ;
} ;
C++ではクラスをテンプレート宣言できる。型テンプレートパラメーターは型として使える。
template < typename T >
class X
{
public :
T member ;
} ;
int main()
{
X<int> i ;
i.member = 42 ; // int
X<double> d ;
d.member = 1.0 ; // double
}
C++では関数を宣言できる。
int f( int x )
{ return x ; }
C++では関数をテンプレート宣言できる。型テンプレートパラメーターは型として使える。
template < typename T >
T f( T x )
{ return x ; }
int main()
{
auto i = f( 42 ) ; // int
auto d = f( 1.0 ) ; // double
}
C++11ではtypedef
名を宣言するためにエイリアス宣言ができる。
using type = int ;
C++11ではエイリアス宣言をテンプレート宣言できる。型テンプレートパラメーターは型として使える。
template < typename T >
using type = T ;
int main()
{
type<int> i = 42 ; // int
type<double> d = 1.0 ; // double
}
そろそろパターンが見えてきたのではないだろうか。C++では一部の宣言はテンプレート宣言できるということだ。このパターンを踏まえて以下を考えてみよう。
C++では変数を宣言できる。
int variable{} ;
C++14では変数宣言をテンプレート宣言できる。型テンプレートパラメーターは型として使える。
template < typename T >
T variable { } ;
int main()
{
variable<int> = 42 ;
variable<double> = 1.0 ;
}
変数テンプレートは名前どおり変数宣言をテンプレート宣言できる機能だ。変数テンプレートはテンプレート宣言なので、名前空間スコープとクラススコープの中にしか書くことができない。
// これはグローバル名前空間スコープという特別な名前空間スコープ
namespace ns {
// 名前空間スコープ
}
class
{
// クラススコープ
} ;
変数テンプレートの使い道は主に2つある。
プログラムでマジックナンバーを変数化しておくのは良い作法であるとされている。たとえば円周率を3.14...
などと書くよりもpi
という変数名で扱ったほうがわかりやすい。変数化すると、円周率の値が後で変わったときにプログラムを変更するのも楽になる。
constexpr double pi = 3.1415926535 ;
しかし、円周率を表現する型が複数ある場合どうすればいいのか。よくあるのは名前を分ける方法だ。
constexpr float pi_f = 3.1415 ;
constexpr double pi_d = 3.1415926535 ;
constexpr int pi_i = 3 ;
// 任意の精度の実数を表現できるクラスとする
const Real pi_r("3.141592653589793238462643383279") ;
しかしこれは、使う側で型によって名前を変えなければならない。
// 円の面積を計算する関数
template < typename T >
T calc_area( T r )
{
// Tの型によって使うべき名前が変わる
return r * r * ??? ;
}
関数テンプレートを使うという手がある。
template < typename T >
constexpr T pi()
{
return static_cast<T>(3.1415926535) ;
}
template < >
Real pi()
{
return Real("3.141592653589793238462643383279") ;
}
template < typename T >
T calc_area( T r )
{
return r * r * pi<T>() ;
}
しかし、この場合引数は何もないのに関数呼び出しのための()
が必要だ。
変数テンプレートを使うと以下のように書ける。
template < typename T >
constexpr T pi = static_cast<T>(3.1415926535) ;
template < >
Real pi<Real>("3.141592653589793238462643383279") ;
template < typename T >
T calc_area( T r )
{
return r * r * pi<T> ;
}
値を返すtraits
で値を得るには::value
と書かなければならない。
std::is_pointer<int>::value ;
std::is_same< int, int >::value ;
C++14ではstd::integral_constant
にconstexpr operator bool
が追加されたので、以下のようにも書ける。
std::is_pointer<int>{} ;
std::is_same< int, int >{} ;
しかしまだ面倒だ。変数テンプレートを使うとtraits
の記述が楽になる。
template < typename T >
constexpr bool is_pointer_v = std::is_pointer<T>::value ;
template < typename T, typename U >
constexpr bool is_same_v = std::is_same<T, U>::value ;
is_pointer_v<int> ;
is_same_v< int, int > ;
C++の標準ライブラリでは従来のtraits
ライブラリを変数テンプレートでラップした_v
版を用意している。
機能テストマクロは__cpp_variable_templates
, 値は201304。