2014年6月12日木曜日

C++11で型がIteratorであるか調べるis_iterator

きっかけはSFINAE

スカラーまたは範囲に対して、同様の操作をそれぞれ定義したいと思った。 もちろんオーバーロードによって同様の関数名として定義したい。 範囲に対する操作を定義するときは、C++標準に倣うとIteratorを使うことが多いので、次のようなコードだ。

template<typename Tin, typename Tio>
void add(Tin first, Tin last, Tio io)
{
  if(first != last)
  {
    *io += *first;
    while(++first != last)
      *++io += *first;
  }
}

template<typename Tio, typename T>
void add(Tio first, Tio last, T v) // error: redefine
{
  if(first != last)
  {
    do
    {
      *first += v;
    }
    while(++first != last);
  }
}

関数のシグネチャが同一なので再定義になっておりコンパイルできぬ。 では3つめの引数の型で呼出を切り替えるようなSFINAEにしてみよう。 これが、思ったよりは簡単でなかったという話。

Iterator #とは

例によってcppreference.com

参照: http://en.cppreference.com/w/cpp/concept/Iterator
分かり難く←書くと、Iteratorを満たす型(以降、Iterator型と書く)は、

  1. CopyConstructibleかつCopyAssignableでDestructibleである。
  2. 左辺値(lvalue)は、Swappableである。
  3. Iterator型Itの参照rに対して++rが有効で、rがDereferenceableなら*rも有効で、それぞれ戻り値型がIt&とstd::iterator_traits<It>::referenceである。
    • Dereferenceableってのは、begin()の前とかend()以後とかぬるぽとか、実体が消えたとかで無効になったやつとか、とにかく逆参照した時に動作未定義じゃないやつ。

is_iteratorを実装する

CopyConstructible・CopyAssignable・Destructible

Iterator型は、CopyConstructibleかつCopyAssignableでDestructibleである。 幸いにして、これらの要件が満たされているかは、標準ライブラリのtype_traitsによって調べることができる。

  • std::is_copy_constructible
  • std::is_copy_assignable
  • std::is_destructible

Swappable

Iterator型の左辺値(lvalue)は、Swappableである。 これはこの前書いたis_swappableで(不完全ながら)調べることができる。 実はこのis_iteratorのためにis_swappableが欲しかった。

PreIncrementable

++rが有効で戻り値型がIt&かどうかを判断するis_pre_incrementableを定義する。 is_swappableの定義とあまり変わらないが、より簡単だ。

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

  template<typename T>
  struct PreIncrementable
  {
    template
    < typename T_ = typename std::add_lvalue_reference<T>::type
    , typename U = decltype(++std::declval<T_>())
    , typename R = T_
    >
    static auto test(std::nullptr_t)->std::is_same<U, R>;
    static auto test(...)->std::false_type;
  };

} // namespace implement

template<typename T>
using is_pre_incrementable = implement::Test<implement:: PreIncrementable<T>>;

Dereferenceable

これを静的にチェックするのはさすがに無理でしょ。ここでは、*rが有効で戻り値型がstd::iterator_traits<It>::referenceかどうかで判断することにしておこう。 便宜上、名前はis_dereferenceableにし、定義はis_pre_incrementableと同様する。

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

  template<typename T>
  struct Dereferenceable
  {
    template
    < typename T_ = typename std::add_lvalue_reference<T>::type
    , typename U = decltype(*std::declval<T_>())
    , typename R
      = typename std::iterator_traits<remove_reference_t<T_&g>::reference
    >
    static auto test(std::nullptr_t)->std::is_same<U, R>;
    static auto test(...)->std::false_type;
  };

} // namespace implement

template<typename T>
using is_dereferenceable = implement::Test<implement:: Dereferenceable<T>>;

Iterator

準備は整った。あとは上記を満たすことを確認するis_iteratorを定義するだけだ。

template<typename T>
using is_iterator
= std::integral_constant
  < bool
  , std::is_copy_constructible<T>::value
    &&std::is_copy_assignable<T>::value
    &&std::is_destructible<T>::value
    &&is_swappable<T>::value
    &&is_pre_incrementable<T>::value
    &&is_dereferenceable<T>::value
  >

まとめ

全部まとめて、動作確認もしてみた。よさげ。

// http://melpon.org/wandbox/permlink/MkAxKPhz8WyYzpBI
#include<cstddef>
#include<iterator>
#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;
    };

    template<typename T>
    struct PreIncrementable
    {
      template
      < typename T_ = typename std::add_lvalue_reference<T>::type
      , typename U = decltype(++std::declval<T_>())
      , typename R = T_
      >
      static auto test(std::nullptr_t)->std::is_same<U, R>;
      static auto test(...)->std::false_type;
    };

    template<typename T>
    struct Dereferenceable
    {
      template
      < typename T_ = typename std::add_lvalue_reference<T>::type
      , typename U = decltype(*std::declval<T_>())
      , typename R
        = typename std::iterator_traits
          < typename std::remove_reference<T_>::type
          >::reference
      >
      static auto test(std::nullptr_t)->std::is_same<U, R>;
      static auto test(...)->std::false_type;
    };

  } // namespace implement

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

  template<typename T>
  using is_pre_incrementable = implement::Test<implement::PreIncrementable<T>>;

  template<typename T>
  using is_dereferenceable = implement::Test<implement::Dereferenceable<T>>;

  template<typename T>
  using is_iterator
  = std::integral_constant
    < bool
    , std::is_copy_constructible<T>::value
      &&std::is_copy_assignable<T>::value
      &&std::is_destructible<T>::value
      &&is_swappable<T>::value
      &&is_pre_incrementable<T>::value
      &&is_dereferenceable<T>::value
    >;

} // namespace skystar0227

#include<vector>

struct Hoge{};
using NotSwappable = std::insert_iterator<std::vector<Hoge>>;

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

#include<iostream>

int main()
{
  using skystar0227::is_iterator;

  /*0*/std::cout << is_iterator<Hoge>::value << std::endl;
  /*0*/std::cout << is_iterator<NotSwappable>::value << std::endl;
  /*0*/std::cout << is_iterator<int>::value << std::endl;
  /*0*/std::cout << is_iterator<int&>::value << std::endl;
  /*1*/std::cout << is_iterator<int*>::value << std::endl;
  /*1*/std::cout << is_iterator<int*&>::value << std::endl;
  /*1*/std::cout
  << is_iterator<decltype(std::begin(std::vector<int>{}))>::value << std::endl;
  /*1*/std::cout
  << is_iterator<std::insert_iterator<std::vector<int>>>::value << std::endl;
  return 0;
}

最初の問題は

SFINAEでIterator型か否かを判断して、スカラーまたは範囲に対する操作をオーバーロードさせることだった。 次のようにすれば、意図したとおりに動作することが確認できる。

// http://melpon.org/wandbox/permlink/pMnNLobBTcy0DkmN
template
< typename Tin, typename Tio
, typename std::enable_if<skystar0227::is_iterator<Tio>::value>::type* = nullptr
>
void add(Tin first, Tin last, Tio io)
{
  if(first != last)
  {
    *io += *first;
    while(++first != last)
      *++io += *first;
  }
}

template
< typename Tio, typename T
, typename std::enable_if<! skystar0227::is_iterator<T>::value>::type* = nullptr
>
void add(Tio first, Tio last, T v) // ok: SFINAE
{
  if(first != last)
  {
    do
    {
      *first += v;
    }
    while(++first != last);
  }
}

int main()
{
  std::vector<int> a(10, 1), b(10, 2);
  int v = 5;
  
  add(std::begin(a), std::end(a), std::begin(b)); // b+=a
  add(std::begin(a), std::end(a), v); //a+=v
  /*6*/for(auto x: a) std::cout << x << ' ';
  std::cout << std::endl;
  /*3*/for(auto x: b) std::cout << x << ' ';
  std::cout << std::endl;

  return 0;
}

0 件のコメント:

コメントを投稿