2020年6月23日火曜日

STLのtype_traitsを実装する その1

C++の標準ライブラリにはtype_traitsという,型に対して様々な判定をしたり,変換をしたりするテンプレートが用意されています.こういうテンプレートをtype traitsと言います.

これを独自に実装するというのは車輪の再発明になりますが,例えばテンプレートで配列の場合のみ特殊化したい場合,どう書けば良いのか,などテンプレートの書き方について色々と勉強になります.

なお,ここではC++17以降を想定しています.

integral_constantの実装

まず,integral_constantを実装します.これは,type_traitsにある色々な判定系のテンプレートの土台になっています.また,boolの場合に限定したbool_constantエイリアステンプレートや,その値がtrueの場合のtrue_type,false_typeは,型が特定の条件を満たすか判定する際の土台として使います.

template <class T, T v>
struct integral_constant {
    static constexpr T value = v;
    using value_type = T;
    using type = integral_constant<T, v>
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator()() const noexcept { return value; }
};
template <bool B>
using bool_constant = integral_constant<bool, B>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;

is_sameおよびis_same_vの実装


次に,2つのテンプレートパラメータの型が同じ場合にtrue,違う場合にfalseとなるvalueを持ったis_sameという型を実装します.また,通常はis_same<X, Y>::valueと書いて値を読み取るところを,C++14以降では,直接読み取れるようにした
is_same_vも追加されているので,そちらも実装します.

template <class T, class U>
struct is_same : false_type {};

template <class T>
struct is_same : true_type {};

template <class T, class U>
inline constexpr bool is_same_v = is_same<T, >::value;

このように,通常はfalse_typeを継承していて,型が同じ場合を特殊化してtrue_typeを継承するようにすることで,valueの値は型が同じときだけtrueになります.

これを実装しておくと,次に実装する,型を変形するtype traitsのテストがしやすくなります.is_sameも,次のようにstatic_assertを利用してテストしておきましょう.

static_assert(is_same_v<int, int>);
static_assert(!is_same_v<int, const int>);

本当は,もっと色々なパターンをテストしておいた方が良いでしょうが,その辺は実装する際に色々考えてみると良いでしょう.

remove/add系の実装


次に,型を変形するtype traitsを実装します.何故かというと,その型が何なのかを判定するis_XXX系のtype_traitsではconstなどは無視するケースも多々あり,そこでこれから実装するremove_constなどを利用するからです.

これらの型を変形するtype traitsには,typeという名前で変形後の型にアクセスできます.

まずは,constを取り除くremove_constを実装しましょう.また,typeに直接アクセスできるremove_const_tも実装します.

template <class T>
struct remove_const
{
    using type = T;
};

template <class T>
struct remove_const
{
    using type = T;
};

template <class T>
using remove_const_t = typename remove_const<T>::type;

エイリアステンプレートでtypeと取り出す際には,typenameが必要です.

逆に,constを付けるadd_constの実装はシンプルです.

template <class T>
struct add_const
{
    using type = const T;
};

template <class T>
using add_const_t = typename add_const<T>::type;

constで無い型にはconstが付き,既にconstな型なら追加のconstは無視されるんですね.

では,テストを書いておきましょう.

static_assert(is_same_v<remove_const<int>, int>);
static_assert(is_same_v<remove_const<const int>, int>);
static_assert(is_same_v<remove_const<const int *>, const int *>);
static_assert(is_same_v<remove_const<const int * const>, const int *>);

本当は,もう少し色々書くべきですが,省略します.ここで注目すべきは,ポインタの場合,constはポインタに対して付いているものが取り除かれる,ということです.
const int *がint *になると思った方もいるかもしれませんが,そうはなりません.

こんな感じで,独自のtype_traitsを実装していきます.

remove_constとadd_constのconstをvolatileに置き換えるだけで,remove_volatileとadd_volatileが作れます.

この2つを組み合わせれば,remove_cvやadd_cvが作れます.

その2は……気が向いたら書きます.

0 件のコメント:

コメントを投稿