オーバーロードされたメンバ関数の有無による条件分岐

メンバ関数の有無による条件分岐をしようと思って調べました。

それ自体はできたのですが、メンバ関数オーバーロードされていると上手くいかなかったのでそれの解決策と実装例を載せたいと思います。

とりあえず、目標としてはイテレータを返り値とする findメンバ関数の有無で分岐するような contains 関数を作りたいと思います。

結論

結論としては下記のような実装例になりました。 #include <bits/stdc++.h>using namespace std; を使用しています(すいません)。

namespace helper {
  template <typename T>
  class has_iterator {
    template <typename Container>
    static true_type test(typename Container::iterator *);
    template <typename Container>
    static false_type test(...);

  public:
    static const bool value = decltype(test<T>(0))::value;
  };

  template <typename Container, typename T>
  class has_find {
    template <typename InnerContainer, int dummy = (static_cast<typename enable_if<has_iterator<InnerContainer>::value, InnerContainer>::type::iterator (InnerContainer::*)(const T &)>(&InnerContainer::find), 0)>
    static true_type check(InnerContainer *);
    static false_type check(...);
    static Container *container;

  public:
    static const bool value = decltype(check(container))::value;
  };
} // namespace helper

template <typename Container, typename T>
bool contains(const Container &container, const T &x) {
  if constexpr (helper::has_find<Container, T>::value) {
    return container.find(x) != end(container);
  } else {
    return find(begin(container), end(container), x) != end(container);
  }
}
#include <bits/stdc++.h>
using namespace std;

namespace helper {
  template <typename T>
  class has_iterator {
    template <typename Container>
    static true_type test(typename Container::iterator *);
    template <typename Container>
    static false_type test(...);

  public:
    static const bool value = decltype(test<T>(0))::value;
  };

  template <typename Container, typename T>
  class has_find {
    template <typename InnerContainer, int dummy = (static_cast<typename enable_if<has_iterator<InnerContainer>::value, InnerContainer>::type::iterator (InnerContainer::*)(const T &)>(&InnerContainer::find), 0)>
    static true_type check(InnerContainer *);
    static false_type check(...);
    static Container *container;

  public:
    static const bool value = decltype(check(container))::value;
  };
} // namespace helper

template <typename Container, typename T>
bool contains(const Container &container, const T &x) {
  if constexpr (helper::has_find<Container, T>::value) {
    return container.find(x) != end(container);
  } else {
    return find(begin(container), end(container), x) != end(container);
  }
}

int main() {
  cout << boolalpha;

  {
    cout << "set-------------" << endl;
    set<int> s;
    s.insert(10);
    cout << contains(s, 10) << endl;
    cout << contains(s, 3) << endl;
  }
  {
    cout << "map-------------" << endl;
    map<int, int> mp;
    mp[2] = 3;
    cout << contains(mp, 3) << endl;
    cout << contains(mp, 2) << endl;
  }
  {
    cout << "vector-------------" << endl;
    vector<int> vs = { 1, 4, 3 };
    cout << contains(vs, 5) << endl;
    cout << contains(vs, 4) << endl;
    cout << contains(vs, 1) << endl;
  }
  {
    // expect : string uses std::find
    cout << "string-------------" << endl;
    string s = "341";
    cout << contains(s, '5') << endl;
    cout << contains(s, '4') << endl;
    cout << contains(s, '1') << endl;
  }
}

実装例を目で追うときははhas_findvalueに何が入るのかというところから追って行ってください。

解決策

オーバーロードされたメンバ関数を指定するにはstatic_castで型を指定すればよいです。

static_cast<返り値(関数ポインタ*)(引数)>(目的のメンバ関数のアドレス)

のようにします。

実装例では

static_cast<省略::iterator (InnerContainer::*)(const T &)>(&InnerContainer::find)

としています。

ここで、イテレータを持たない型が入ってきたときのためにenable_ifhas_iterator<T>::valuetruefalseかを判別しています。

返り値の型がtrue_typeになっているcheck関数のテンプレート部分でエラーが起きると、返り値の型がfalse_typecheck関数に移ります。


基本的な説明はこちらの記事がとても分かりやすかったです。

あとがき

オーバーロードされたメンバ関数を指定するためにはstatic_castで型を指定する必要があり、メンバ関数の有無を判定するものは全てには対応させられないのではないかと思います。(マクロならできるんですかね?)

あるオーバーロードされたメンバ関数の有無を調べるには局所的な実装にならざるを得ない...?