C++ において、ある名前のメンバ関数をクラスが持っているかどうか、また、そのメンバ関数が想定する型に一致するかどうかを判定するトレイツの定義を以前に紹介しました。
しかし、この手法は型チェックが厳密すぎます。 必要以上に制約を厳しくしてしまう可能性があります。
まず、説明のため int
を受け取って int
を返すメンバ関数 foo
を持っているかどうか判定するトレイツを同じ手法で作ってみます。
foo_traits.h
// -*- mode:c++ -*-
#include <type_traits>
template<class T>
class has_foo {
template<class U, int (U::*)(int)>
struct helper_t { typedef T type; };
template<class U, class V = T>
struct helper : std::false_type {};
template<class U>
struct helper<U, typename helper_t<U, &U::foo>::type> : std::true_type {};
public:
static const bool value = helper<T>::value;
};
このとき、以下のようなクラスたちを判定するとどうなるでしょうか?
sample.cpp
#include "foo_traits.h"
class sample1 {
public:
int foo(int x) {
return x;
}
};
class sample2 {
public:
char foo(int x) {
return x;
}
};
class sample3 {
public:
int foo(long int x) {
return x;
}
};
class sample4 {
public:
template<class T>
T foo(T x) {
return x;
}
};
#include <iostream>
int main(void) {
std::cout << has_foo<sample1>::value << std::endl;
std::cout << has_foo<sample2>::value << std::endl;
std::cout << has_foo<sample3>::value << std::endl;
std::cout << has_foo<sample4>::value << std::endl;
return 0;
}
結果はこうなります。
$ clang++ sample.cpp -std=c++11
$ ./a
1
0
0
1
しかし、返却値が char
型のときにそれを int
型の変数で受けても汎整数拡張でうまいこと処理されますし、仮引数が long int
型のところに int
型の値を渡そうとしても同様です。 foo
が実質的に int
型の値を受け取って int
型の値を返すと看做せるような場合には真を返すような緩い判定をする方法を考えてみました。
foo_traits.h
// -*- mode:c++ -*-
#include <type_traits>
template<class T>
class has_foo {
template<class U, class V = T>
struct helper : std::false_type {};
template<class U>
struct helper<U, typename std::enable_if<std::is_convertible<decltype(static_cast<U*>(nullptr)->foo(int())), int>::value, T>::type> : std::true_type {};
public:
static const bool value = helper<T>::value;
};
decltype
の中に実際に int
型の値を渡す式を書いてみるという方法で判定しています。 また、返却値の型の判定には is_convertible
を使いました。
このトレイツを使うと上述のサンプルはいずれも真を返します。
$ clang++ sample.cpp -std=c++11
$ ./a
1
1
1
1