パート LIII. メタ関数 ( C++17 )

目次

335. std::bool_constant やテンプレートのテクを使ってチェックする
336. std::enable_if_t を使って便利にする

メタ関数はクラステンプレートや、テンプレート関数を使えば作ることは可能ですが、複雑なものを作ろうとすると難易度が高くなりがちでした。

C++17 で導入されたメタ関数は 3 つありますが、どれも実装難易度をかなり下げてくれる可能性があります。

std::conjunction
論理ANDをするメタ関数
std::disjunction
論理ORをするメタ関数
std::negation
論理NOTをするメタ関数

うち2つは可変長テンプレート引数を前提とします。

// <type_traits>
template<class... B>
struct conjunction;

template<class... B>
struct disjunction;

template<class B>
struct negation;

例えば「B1,B2,…,Bi,…,Bn-1,Bn」といったテンプレート引数があったとします。

このテンプレートで指定した型には 「B1::value,B2::value,…,Bi::value,…,Bn-1::value,Bn::value」というようにブーリアン型に変換が可能な value というメンバー変数があるものとします。

std::conjunction<B0,B1,…,Bi,…,Bn>::value は以下のような場合に true となります。

bool(B0::value)==true
bool(B1::value)==true
...
bool(Bi::value)==true
...
bool(Bn::value)==true

ちなみに std::conjunction<B0,B1,…,Bi,…,Bn>::value は std::conjunction_v<B0,B1,…,Bi,…,Bn> と簡略化することが可能です。

template<class... B>
inline constexpr bool conjunction_v = conjunction<B...>::value;

std::conjunction は論理積を表すメタ関数でありリファレンス実装は以下のようなものとなります。

template<class...> struct conjunction : std::true_type { };
template<class B1> struct conjunction<B1> : B1 { };
template<class B1, class... Bn>
struct conjunction<B1, Bn...>
    : std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {};

std::disjunction は論理和を表すメタ関数です。

std::disjunction<B0,B1,…,Bi,…,Bn>::value は Bi::value に1つでも true があれば true となります。

bool(B0::value)==true
bool(B1::value)==false
...
bool(Bi::value)==true
...
bool(Bn::value)==false

この例では true が 1 つでもあるので std::disjunction の value の保持する値は true となります。

これは論理 OR と同じ挙動となりますね。

std::disjunction<B0,B1,…,Bi,…,Bn>::value は std::disjunction_v<B0,B1,…,Bi,…,Bn> と簡略化することが可能です。

template<class... B>
inline constexpr bool disjunction_v = disjunction<B...>::value;

std::negation<B> は B::value が true なら false になります。 B::value が false なら true になるので、真偽値が逆になる効果を持ちます。

template<class B>
inline constexpr bool negation_v = negation<B>::value;

じゃあ軽く実装してみましょうかね。

注記

マクロについては クロスコンパイルのための組み込みマクロ を参照くださいね。

main.cpp. 

  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<typename T>
 16 struct TrueType {
 17   static constexpr bool value = true;
 18 };
 19
 20 template<typename T>
 21 struct FalseType {
 22   static constexpr bool value = false;
 23 };
 24
 25 static_assert(
 26   std::conjunction<TrueType<int>,TrueType<double>,TrueType<long>>::value
 27 );
 28
 29 static_assert(
 30   std::disjunction<TrueType<float>,FalseType<double>>::value
 31 );
 32
 33 static_assert(
 34   std::negation<FalseType<float>>::value
 35 );
 36
 37 static_assert(
 38   std::negation<std::disjunction<FalseType<void>,FalseType<bool>,FalseType<int>>>::value
 39 );
 40
 41 int main(){}

この実装例は「 bool(B0::value) 」が常に true か false かを返す 2 つの型を使用しています。

 15 template<typename T>
 16 struct TrueType {
 17   static constexpr bool value = true;
 18 };
 19
 20 template<typename T>
 21 struct FalseType {
 22   static constexpr bool value = false;
 23 };

つまり TrueType の 「 bool(B0::value) 」、「 bool(B1::value) 」、「 bool(B2::value) 」、「 bool(Bn::value) 」の全てが true を返すということですね。

FalseType は逆で全ての value が false として評価されます。

std::conjunction は論理積(AND)ということで value が true になるためには全てのテンプレート引数の value が true である必要があります。

 25 static_assert(
 26   std::conjunction<TrueType<int>,TrueType<double>,TrueType<long>>::value
 27 );

これは全てのテンプレート引数で指定された型の value が true なので std::conjunction の value は true になります。

std::disjunction は論理和(OR)なので、FalseType を入れても value は true に評価されます。

 29 static_assert(
 30   std::disjunction<TrueType<float>,FalseType<double>>::value
 31 );

悪い例も考えて見ましょう…

例えば論理和にも論理積のベースとなる型に value 定数が定義されていない空の場合を考えて見ましょう。

注記

マクロについては クロスコンパイルのための組み込みマクロ を参照くださいね。

main.cpp. 

  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<typename T>
 16 struct AnyType{};
 17
 18 static_assert(
 19   std::disjunction<AnyType<float>,AnyType<double>>::value
 20 );
 21
 22 static_assert(
 23   std::conjunction<AnyType<float>,AnyType<double>>::value
 24 );
 25
 26 int main(){}

std::disjunction と std::conjunction がチェックする AnyType 型には bool 型の value 変数が定義されてませんね。

なので論理積も論理和も意味はないんですが、両方とも true になっちゃってますね。

ですが true になったからって、評価する式が抜け落ちてるんですから何もしてないわけです。

こういう型の実装を忘れるのは、頻繁でなくともたまにやっちゃいそうなミスです。

ではもう少し複雑な実装例も見てみましょう。

常に true とか false を返す型でなく、例えば整数型なら true でそれ以外は false を返すような型なんかどうでしょうかね?

注記

マクロについては クロスコンパイルのための組み込みマクロ を参照くださいね。

main.cpp. 

    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<typename T>
   16 struct IntegerType{
   17   static constexpr bool value = std::is_integral<T>::value;
   18 };
   19
   20 template<typename T>
   21 struct FloatingType{
   22   static constexpr bool value = std::is_floating_point<T>::value;
   23 };
   24
>> 25 static_assert(
   26   std::disjunction<IntegerType<float>,IntegerType<double>>::value
   27 );
   28
   29 static_assert(
   30   std::disjunction<FloatingType<float>,FloatingType<double>>::value
   31 );
   32
   33 int main(){}

このソースコードは 25 行目に問題があるためコンパイルしません。

まずはベース型の定義です。

   15 template<typename T>
   16 struct IntegerType{
   17   static constexpr bool value = std::is_integral<T>::value;
   18 };
   19
   20 template<typename T>
   21 struct FloatingType{
   22   static constexpr bool value = std::is_floating_point<T>::value;
   23 };

IntegerType は整数型、FloatingType の value は浮動小数点型のときのみに true が設定されます。

以下の場合では Floatingtype のテンプレート引数に指定した型が float と double とも浮動小数点型でない場合にコンパイルエラーとなります。

この場合 float と double の両方とも浮動小数点型なのでコンパイルエラーにはなりません。

   29 static_assert(
   30   std::disjunction<FloatingType<float>,FloatingType<double>>::value
   31 );

それでコンパイルエラーとなっている箇所は以下の行です。

>> 25 static_assert(
   26   std::disjunction<IntegerType<float>,IntegerType<double>>::value
   27 );

IntegerType のテンプレート引数に float や double を指定しています。

論理和(OR)なので、いずれかでも真であればエラーにはなりませんが、この箇所では両方とも整数型ではないので、コンパイルエラーとなります。

std::disjunction には一つでも true があれば良いのですが IntegerType<float> と IntegerType<double> の両方とも false なので static_assert は失敗します。

もう少し難しめの使用例も考えてみました。

注記

マクロについては クロスコンパイルのための組み込みマクロ を参照くださいね。

main.cpp. 

  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 void_conjunction_t {
 17   static constexpr bool value = std::conjunction_v<std::is_void<T>...>;
 18 };
 19
 20 template<class... T>
 21 struct void_disjunction_t {
 22   static constexpr bool value = std::disjunction_v<std::is_void<T>...>;
 23 };
 24
 25 static_assert(void_conjunction_t<void,void,void>::value);
 26 static_assert(void_disjunction_t<long,double,void>::value);
 27 static_assert(std::negation<void_conjunction_t<void,long,int>>::value);
 28
 29 int main(){}

このファイルはビルドしてもコンパイルエラーは出ません。

テンプレート引数が全て void 型かのチェックを一括で行ってくれるのが void_conjunction_t クラステンプレートです。

 15 template<class... T>
 16 struct void_conjunction_t {
 17   static constexpr bool value = std::conjunction_v<std::is_void<T>...>;
 18 };

一番重要なのが value 変数が bool 型ということですかね。

チェックした結果は value 変数の中に入ってるので、そこをアサートします。

 25 static_assert(void_conjunction_t<void,void,void>::value);

これだけです。

テンプレート引数のいずれかでも void 型かの一括チェックを行ってくれるのは void_disjunction_t クラステンプレートになります。

 20 template<class... T>
 21 struct void_disjunction_t {
 22   static constexpr bool value = std::disjunction_v<std::is_void<T>...>;
 23 };

結果を取り出すには以下のようにします。

 26 static_assert(void_disjunction_t<long,double,void>::value);

第三テンプレート引数が void 型なので、これは通ります。

最後に std::negation となります。

 27 static_assert(std::negation<void_conjunction_t<void,long,int>>::value);

これは void_conjuntion_t の結果の真偽を引っくり返します。

void_conjuntion_t は偽なので value には真が入っています。

Copyright 2017-2018, by Masaki Komatsu