型特性ライブラリ:型の比較

前回の投稿「型特性ライブラリ:型チェック」では、型特性ライブラリを使用したコンパイル時の型チェックを紹介しました。今日は、コンパイル時の型比較について書きます。

型特性ライブラリを使用すると、コンパイル時に型を比較できます。コンパイル時とは、実行時にコストがかからないことを意味します。

型の比較

型特性ライブラリは、C++11 で 3 種類の比較をサポートします:

  • is_base_of<ベース、派生>
  • is_convertible
  • 同じです

C++20 では、さらに

  • is_pointer_interconvertible_with_class
  • is_pointer_interconvertible_base_of<ベース、派生>

簡単にするために、C++11 メタ関数についてのみ記述します。

そのメンバー値のおかげで、各クラス テンプレートは true または false を返すため、static_assert に最適です。

// compare.cpp

#include <cstdint>
#include <iostream>
#include <type_traits>

class Base{};
class Derived: public Base{};

int main(){
 
 std::cout << std::boolalpha << '\n';
 
 std::cout << "std::is_base_of<Base,Derived>::value: " << std::is_base_of<Base,Derived>::value << '\n';
 std::cout << "std::is_base_of<Derived,Base>::value: " << std::is_base_of<Derived,Base>::value << '\n';
 std::cout << "std::is_base_of<Derived,Derived>::value: " << std::is_base_of<Derived,Derived>::value << '\n';
 
 // static_assert(std::is_base_of<Derived,Base>::value,"Derived is not base of Base"); // (1) 
 
 std::cout << '\n';
 
 std::cout << "std::is_convertible<Base*,Derived*>::value: " << std::is_convertible<Base*,Derived*>::value << '\n';
 std::cout << "std::is_convertible<Derived*,Base*>::value: " << std::is_convertible<Derived*,Base*>::value << '\n';
 std::cout << "std::is_convertible<Derived*,Derived*>::value: " << std::is_convertible<Derived*,Derived*>::value << '\n';
 
 // static_assert(std::is_convertible<Base*,Derived*>::value,"Base* can not be converted to Derived*"); // (2)
 
 std::cout << '\n';
 
 std::cout << "std::is_same<int, int32_t>::value: " << std::is_same<int, int32_t>::value << '\n';
 std::cout << "std::is_same<int, int64_t>::value: " << std::is_same<int, int64_t>::value << '\n';
 std::cout << "std::is_same<long int, int64_t>::value: " << std::is_same<long int, int64_t>::value << '\n';
 
 // static_assert(std::is_same<int, int64_t>::value,"int is not the same type as int64_t"); // (3)
 
 std::cout << '\n';
 
}

プログラムの出力に驚かないでください。

static_assert を使用する場合 行 (1)、(2)、および (3) では、コンパイル時にアサーションが発生します。

Web サイト cppreference.com には、すべてのメタ関数 std::is_base_of,std::is_convertible, の可能な実装が含まれています。 、および std::is_same です。それらを研究することは非常に興味深く、やりがいがあります。

可能な実装

まず、3 つのメタ関数の可能な実装を次に示します。 std::is_same に基づく最も単純なものから始めましょう。

std::is_same

次の例では、namespace rgr を使用しています。 私の実装を C++ 標準実装と区別するため。

// isSame.cpp

#include <iostream>
#include <type_traits>

namespace rgr {

 template<class T, T v>
 struct integral_constant {
 static constexpr T value = v;
 typedef T value_type;
 typedef integral_constant type;
 constexpr operator value_type() const noexcept { return value; }
 constexpr value_type operator()() const noexcept { return value; } //since c++14
 };

 typedef integral_constant<bool, true> true_type; // (2) 
 typedef integral_constant<bool, false> false_type;
 
 template<class T, class U>
 struct is_same : false_type {}; // (3)
 
 template<class T>
 struct is_same<T, T> : true_type {};

}

int main() {

 std::cout << '\n';

 std::cout << std::boolalpha;

 std::cout << "rgr::is_same<int, const int>::value: " 
 << rgr::is_same<int, const int>::value << '\n'; // (1)
 std::cout << "rgr::is_same<int, volatile int>::value: " 
 << rgr::is_same<int, volatile int>::value << '\n';
 std::cout << "rgr::is_same<int, int>::value: " 
 << rgr::is_same<int, int>::value << '\n';

 std::cout << '\n';

 std::cout << "std::is_same<int, const int>::value: " 
 << std::is_same<int, const int>::value << '\n';
 std::cout << "std::is_same<int, volatile int>::value: " 
 << std::is_same<int, volatile int>::value << '\n';
 std::cout << "std::is_same<int, int>::value: " 
 << std::is_same<int, int>::value << '\n';

 std::cout << '\n';

}

簡単なメモ:関数テンプレートの呼び出し rgr::is_same<int, const int> (1 行目) 式 rgr::false_type::value の呼び出しを引き起こします (2 行目) std::is_same<> のため false_type から派生 (3 行目)。 rgr::false_type::value rgr::integral_constant<bool, false>::value のエイリアスです (2行目)。この例では、静的な constexpr を使用しています クラス integral_constant の値 . integral_constant 型特性関数の基本クラスです。

次の出力を調べると、興味深い事実が 2 つあります。私の実装 rgr::is_same std::is_same のような振る舞い 、および constvolatile タイプの一部です。

メタ関数 is_same に基づいてメタ関数 isSameIgnoringConstVolatile を実装するのは非常に簡単です。

// isSameIgnoringConstVolatile.cpp

#include <iostream>
#include <type_traits>

namespace rgr {

 template<class T, T v>
 struct integral_constant {
 static constexpr T value = v;
 typedef T value_type;
 typedef integral_constant type;
 constexpr operator value_type() const noexcept { return value; }
 constexpr value_type operator()() const noexcept { return value; } //since c++14
 };

 typedef integral_constant<bool, true> true_type; 
 typedef integral_constant<bool, false> false_type;

 template<class T, class U>
 struct is_same : false_type {};
 
 template<class T>
 struct is_same<T, T> : true_type {};
 
 template<typename T, typename U> // (1)
 struct isSameIgnoringConstVolatile: rgr::integral_constant<
 bool,
 rgr::is_same<typename std::remove_cv<T>::type, 
 typename std::remove_cv<U>::type>::value 
 > {};

}

int main() {

 std::cout << '\n';

 std::cout << std::boolalpha;

 std::cout << "rgr::isSameIgnoringConstVolatile<int, const int>::value: " 
 << rgr::isSameIgnoringConstVolatile<int, const int>::value << '\n';
 std::cout << "rgr::is_same<int, volatile int>::value: " 
 << rgr::isSameIgnoringConstVolatile<int, volatile int>::value << '\n';
 std::cout << "rgr::isSameIgnoringConstVolatile<int, int>::value: " 
 << rgr::isSameIgnoringConstVolatile<int, int>::value << '\n';

 std::cout << '\n';

}

メタ関数 isSameIgnoringConstVolatile rgr::integral_constant から派生 関数 std::remove_cvを使用します const を削除するには または volatile その種類から。ご想像のとおり、std::remove_cv 型特性ライブラリの関数であり、コンパイル時に型を変更できます。型の変更については、次の投稿で詳しく書きます。

最後に、プログラムの出力は次のとおりです。

2 つのメタ関数 std::is_base_of と std::is_convertible を詳しく見てみましょう。 . 以下は、cppreference.com からの可能な実装です。

std::is_base_of

namespace details {
 template <typename B>
 std::true_type test_pre_ptr_convertible(const volatile B*);
 template <typename>
 std::false_type test_pre_ptr_convertible(const volatile void*);
 
 template <typename, typename>
 auto test_pre_is_base_of(...) -> std::true_type;
 template <typename B, typename D>
 auto test_pre_is_base_of(int) ->
 decltype(test_pre_ptr_convertible<B>(static_cast<D*>(nullptr)));
}
 
template <typename Base, typename Derived>
struct is_base_of :
 std::integral_constant<
 bool,
 std::is_class<Base>::value && std::is_class<Derived>::value &&
 decltype(details::test_pre_is_base_of<Base, Derived>(0))::value
 > { };

std::is_convertible

namespace detail {
 
template<class T>
auto test_returnable(int) -> decltype(
 void(static_cast<T(*)()>(nullptr)), std::true_type{}
);
template<class>
auto test_returnable(...) -> std::false_type;
 
template<class From, class To>
auto test_implicitly_convertible(int) -> decltype(
 void(std::declval<void(&)(To)>()(std::declval<From>())), std::true_type{}
);
template<class, class>
auto test_implicitly_convertible(...) -> std::false_type;
 
} // namespace detail
 
template<class From, class To>
struct is_convertible : std::integral_constant<bool,
 (decltype(detail::test_returnable<To>(0))::value &&
 decltype(detail::test_implicitly_convertible<From, To>(0))::value) ||
 (std::is_void<From>::value && std::is_void<To>::value)
> {};

std::is_same を説明した理由がわかりましたか? .これが私の課題です。

私の挑戦

型特性関数の以前の実装について説明してください std::is_base_of そして std::is_convertible .このメールアドレスは、スパムボットから保護されています.表示するには JavaScript を有効にする必要があります。木曜日(12月2日)まで。各関数の最良の回答には、LeanPub バンドルのモダン C++ コレクションのクーポンが与えられます。

次の投稿で各機能のベストアンサーを公開し、あなたの名を挙げます。あなたのフルネームを言うべきなら、それを書いてください。

次は?

型特性ライブラリのおかげで、コンパイル時に型を変更できます。それについては次の投稿で書きます。