2014年5月30日金曜日

C++11でis_swappableを実装したかった

型特性を調べる

ヘッダファイル<type_traits>をインクルードすると、標準ライブラリによって型特性を調べることができるようになる。std::is_sameとかstd::is_constructibleとか。 大体のものはそれだけで事足りるが、今回はある型がSwappableかどうか確認したくなったので実装してみようと思った。

is_swappableについて

Swappableとは

参照:http://en.cppreference.com/w/cpp/concept/Swappable
本当はC++規格書とか引用するべきだろうけど、ここではオンラインのリファレンスサイトで…

ざっくりというと、次を満たせば良いみたい(著しく正確性を欠く表現)。

  1. 型Tのオブジェクトtと型Uのオブジェクトuにおいて、文脈上"swap(t, u)"と"swap(u, t)"が有効であること
  2. swapしたら、tとuの値が入れ替わってること

1を調べる

コード以上に正確に伝えられる言語を習得していないので、コードから。

// http://melpon.org/wandbox/permlink/k0L4sbTFBEuqK7vm
#include<cstddef>
#include<type_traits>
#include<utility>

namespace skystar0227
{
  namespace implement
  {
    template<typename T> using Test = decltype(T::test(nullptr));

    namespace swappable
    {
      using std::swap;

      template<typename T0, typename T1>
      using CallTest
      = decltype
        ( swap(std::declval<T0>(), std::declval<T1>())
        , swap(std::declval<T1>(), std::declval<T0>())
        );

    } // namespace swappable

    template<typename T0, typename T1>
    struct Swappable
    {
      template
      < typename T0_ = typename std::add_lvalue_reference<T0>::type
      , typename T1_ = typename std::add_lvalue_reference<T1>::type
      , typename = swappable::CallTest<T0_, T1_>
      >
      static auto test(std::nullptr_t)->std::true_type;
      static auto test(...)->std::false_type;
    };

  } // namespace implement

  template<typename T0, typename T1 = T0>
  using is_swappable = implement::Test<implement::Swappable<T0, T1>>;

} // namespace skystar0227

class Foo{};
class Bar: Foo{};

namespace std
{
  template<> void swap<Foo>(Foo&, Foo&) noexcept = delete;

} // namespace std

class Hoge{};

namespace hoge
{
  class Hoge{};
  void swap(Hoge&, Hoge&) = delete;

} // namespace hoge

#include<iostream>

auto main()->int
{
  using skystar0227::is_swappable;
  /*0*/std::cout << is_swappable<void>::value << std::endl;
  /*1*/std::cout << is_swappable<int>::value << std::endl;
  /*0*/std::cout << is_swappable<int const>::value << std::endl;
  /*1*/std::cout << is_swappable<int&>::value << std::endl;
  /*1*/std::cout << is_swappable<int&&>::value << std::endl;
  /*0*/std::cout << is_swappable<Foo>::value << std::endl;
  /*1*/std::cout << is_swappable<Bar>::value << std::endl;
  /*1*/std::cout << is_swappable<Hoge>::value << std::endl;
  /*0*/std::cout << is_swappable<hoge::Hoge>::value << std::endl;
  return 0;
}

次のis_swappableが実体。実装はimplement::Swappableへ。最終的にstd::false_typeかstd::true_typeかに落ち着く。

  template<typename T0, typename T1 = T0>
  using is_swappable = implement::Test<implement::Swappable<T0, T1>>;

implement::Testは、SFINAEを利用して定義される静的メンバ関数testの戻り値型を取得する。これがテスト結果となる。nullptrを渡すのは、多重定義を曖昧にしないため。

    template<typename T> using Test = decltype(T::test(nullptr));

implement::Swappableで、静的メンバ関数testを定義する。デフォルトテンプレート引数を利用したC++11的なSFINAEを採用。参考にしたページを失念した…

    template<typename T0, typename T1>
    struct Swappable
    {
      template
      < typename T0_ = typename std::add_lvalue_reference<T0>::type
      , typename T1_ = typename std::add_lvalue_reference<T1>::type
      , typename = swappable::CallTest<T0_, T1_>
      >
      static auto test(std::nullptr_t)->std::true_type;
      static auto test(...)->std::false_type;
    };

using std::swap;を書きたいがためにnamespace swappableを別途定義して呼出テスト。

    namespace swappable
    {
      using std::swap;

      template<typename T0, typename T1>
      using CallTest
      = decltype
        ( swap(std::declval<T0>(), std::declval<T1>())
        , swap(std::declval<T1>(), std::declval<T0>())
        );

    } // namespace swappable

main関数は、ひと通り意図した挙動を示すか確認できるコードを書いてみた。

2を調べる

無理じゃね?コンパイル時に調べる方法はちょいと思いつかない。

結論:妥協しよう

ということで、不完全だが、is_swappableを実装してみた。少なくともswap関数を呼び出せるかの確認には使えるさ。私は満足だ。

0 件のコメント:

コメントを投稿