LoginSignup
0
0

More than 3 years have passed since last update.

メンバ関数の存在を調べるテンプレートメタ関数を量産する

Last updated at Posted at 2019-08-15

目標に対して妥協点まではきた気がする

僕よりも優れた先人のサンプルコード

を参考に、

  • メンバ関数の有無のみを確認する
  • メンバ変数は除外
  • いろいろな引数の関数に対応する

を量産するマクロを作成してみました。
変数を除外したのは、変数と関数を区別したかったためです。

妥協点までいったような、というコード全容はこちらです。


#include <iostream>
#include <vector>

namespace has_member_function { 

#define HAS_MEMBER_FUNCTION( FunctionName, UniqueID, ... ) \
template < typename T > \
class FunctionName ## UniqueID { \
\
  template< typename Arg > \
  static auto impl( ... ) -> std :: false_type; \
\
  template < typename Arg > \
  static auto impl( Arg* ) -> \
  decltype( \
      std :: declval < Arg >() . FunctionName( __VA_ARGS__ ) \
    , std :: true_type() \
  ); \
\
  public: \
    static constexpr bool value = decltype( impl< T >(nullptr) ) :: value ; \
};

HAS_MEMBER_FUNCTION( size,,  ); // check is T.size()  exist
HAS_MEMBER_FUNCTION( begin,, ); // check is T.begin() exist
HAS_MEMBER_FUNCTION( end,,   ); // check is T.end()   exist
HAS_MEMBER_FUNCTION( empty,, ); // check is T.empty() exist

}

void print( const bool value ){ 
  std :: cout << std :: boolalpha << value << std :: endl;
}

struct A {  };
struct B {  };
struct C {  
  void fx ( const A&, const B& );
};

struct D {
    int func;
};

namespace has_member_function{ 
  HAS_MEMBER_FUNCTION( at, , 0 );
  HAS_MEMBER_FUNCTION( fx, void, );
  HAS_MEMBER_FUNCTION( fx, A_B, A(), B() );
}

int main()
{


  print( has_member_function :: size < std :: vector< int > > :: value );
  print( has_member_function :: begin< std :: vector< int > > :: value );
  print( has_member_function :: end  < std :: vector< int > > :: value );
  print( has_member_function :: empty< std :: vector< int > > :: value );

  print( has_member_function :: at   < std :: vector< int > > :: value );

  print( has_member_function :: fxA_B < C > :: value );

  print( has_member_function :: fxvoid < D > :: value );

  return 0;
}

ざっくりマクロ解説

例えば

namespace has_member_function{
 HAS_MEMBER_FUNCTION(size, , );
}

namespace has_member_function {
template < typename T > 
class size {

  template< typename Arg >
  static auto impl( ... ) -> std :: false_type;

  template < typename Arg >
  static auto impl( Arg* ) ->
  decltype(
      std :: declval < Arg >() . size( )
    , std :: true_type()
  );
  public: 
    static constexpr bool value = decltype( impl< T >(nullptr) ) :: value ;
};
}

と展開され、 size() の有無を確認したいクラス C に対して

has_member_function :: size<C> :: value

と利用することができます。

マクロの詳細

作成したマクロは

HAS_MEMBER_FUNCTION(FunctionName, UniqueID, ...)

と2つ以上の引数をとります。

FunctionName

確認したいメンバ関数名です。同時に生成するクラス名の一部になります。

UniqueID

オーバーロード等、同名のメンバ関数を区別するための識別子として利用します。

  • 検証するクラス C
  • 検証するメンバ関数 test_func()
  • 生成する際のマクロHAS_MEMBER_FUNCTION(test_func, 01, )

とした場合、生成されるメタ関数、及びそれを利用する方法は

test_func01<C> :: value // check C :: test_func() 

となります。

...

検証するメンバ関数の引数を UniqueID 以降に記述します。
サンプルコードでは

  HAS_MEMBER_FUNCTION( at, , 0 );           // example -> std :: vector<...>.at(...)
  HAS_MEMBER_FUNCTION( fx, A_B, A(), B() ); // example -> check C.fx(...)

このように利用しています。

ここまでの変数を用いてメンバ関数を検証しているのは

  decltype( \
      std :: declval < Arg >() . FunctionName( __VA_ARGS__ ) \ //<- Here
    , std :: true_type() \
  ); \

この部分になります。fx(const A&, const B&) メンバ関数を検証するメタ関数を HAS_MEMBER_FUNCTION( fx, A_B, A(), B() );
で生成すると、各引数が展開され

  decltype(
      std :: declval < Arg >().fx( A(), B() ) //<- Here
    , std :: true_type()
  );

と変化します。

動作サンプルと実行結果

std :: vector<int> 型や自作の型をサンプルにメンバ関数の検証をしました。

検証用メタ関数生成

namespace has_member_function {
  HAS_MEMBER_FUNCTION( size,,  ); // check is T.size()  exist
  HAS_MEMBER_FUNCTION( begin,, ); // check is T.begin() exist
  HAS_MEMBER_FUNCTION( end,,   ); // check is T.end()   exist
 HAS_MEMBER_FUNCTION( empty,, ); // check is T.empty() exist
}

namespace has_member_function{ 
  HAS_MEMBER_FUNCTION( at, , 0 );
  HAS_MEMBER_FUNCTION( fx, void, );
  HAS_MEMBER_FUNCTION( fx, A_B, A(), B() );
}

検証用クラス宣言

struct A {  };
struct B {  };
struct C {  
  void fx ( const A&, const B& );
};

struct D {
    int func;
};

プリントデバッグ用関数

void print( const bool value ){ 
  std :: cout << std :: boolalpha << value << std :: endl;
}

main関数

int main()
{
  print( has_member_function :: size < std :: vector< int > > :: value );
  print( has_member_function :: begin< std :: vector< int > > :: value );
  print( has_member_function :: end  < std :: vector< int > > :: value );
  print( has_member_function :: empty< std :: vector< int > > :: value );

  print( has_member_function :: at   < std :: vector< int > > :: value );

  print( has_member_function :: fxA_B < C > :: value );

  print( has_member_function :: fxvoid < D > :: value );

  return 0;
}

実行結果

Start

true
true
true
true
true
true
false

0

Finish

ここまでの右往左往

std :: function に代入できるかで判定するというのも考えました。が、
std :: vector<...>.at(...) の引数とか戻り値とかを調べたり記述したりするとすごく冗長になってしまう気がしたので頓挫しました。
// でも at の引数に 0 渡して確認ってうーーーーん。

メンバに対するアドレスを取得する方法だとメンバ変数も入ってしまうのでこれも除外しました。

8/16 2:16 JST

あ、std :: is_function ...
でも引数の型を比べれないのか。

終わりに

もっとスマートな方法、もっとクレイジーな方法あると思いますので、思いついたら方はコメントであるとか記事を作っていただけたらと思います。

クリエイティブ・コモンズ・ライセンス

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0