8

'void (ClassType::Function)(ArgType)' 型に準拠するメンバー関数をテンプレート化されたクラスでラップしたいと考えています。後で、ClassType のインスタンスをこのテンプレートのインスタンスに渡し、ラップされたメソッドを呼び出すようにします。

class Foo {
 public:
  Foo() : f_(0.0) {}
  void set(double v) { f_ = v * 2.1; }
  double get() { return f_; }
 private:
  double f_;
};

template <typename ArgType, typename ClassType, void (ClassType::*Method)(ArgType)>
class Wrapper {
 public:
  explicit Wrapper(ClassType *cls) : cls_(cls) {}

  void do_something(ArgType value) {
    (cls_->*Method)(value);
  }

 private:
  ClassType *cls_;
};

#include <iostream>
int main(int argc, char ** argv) {
  Foo foo;
  Wrapper<double, Foo, &Foo::set> wrapper(&foo);

  wrapper.do_something(1.0);
  std::cout << foo.get() << std::endl;
  // outputs "2.1"
  return 0;
}

Wrapper<> のインスタンス化で、「Foo」が 2 回指定されていることに注意してください。ここでは冗長に見えます。

そこで知りたいのは、テンプレート パラメーターClassTypeを回避できるかどうかです。たとえば、メンバー関数のポインター パラメーターから暗示または抽出できる場合は、Wrapper<> のインスタンス化で明示的に指定する必要はありません。

同様に、(おそらく) Foo::set ?

これは C++ で可能ですか? おそらく、これらの(完全に幻想的な)線に沿った何か:

template <void (ClassType::*Method)(ArgType)>
class Wrapper2 {
 public:
  explicit Wrapper(Method::ClassType *cls) : cls_(cls) {}

  void do_something(Method::ArgType value) {
    (cls_->*Method)(value);
  }

 private:
  Method::ClassType *cls_;
};

// ...

int main() {
  Foo foo;
  Wrapper<&Foo::set> wrapper(&foo);
  // ...
}

または、おそらく、これらの行に沿って何かを実行する、別のレベルのテンプレート マジックを呼び出すことができます。

Wrapper<Magic<&Foo::set> > wrapper(&foo);

もしあれば、どのようなメカニズムが利用できるか知りたいです。

私は要件として C++11 ではなく C++03 を使用していますが、C++11 が提供するものについても知りたいと思っています。

編集: 詳細 - このメカニズムを使用して ~300 のメンバー関数 (すべて ClassType に属するか、非常に類似したクラスのセット) をラップするつもりですが、考慮すべきシグネチャは約 6 つしかありません。

  • void (ClassType::Function)(ArgType) - ArgType は「フローティング」です
  • void (ClassType::Function)(ArgType) - ArgType は「整数」です
  • void (クラスタイプ::関数)(ブール値)
  • void (ClassType::Function)(IndexType, ArgType) - 追加の 'index' 引数を持つ上記の 3 つ

メンバー関数は、たとえば、(上記の単純な Foo ではなく) 大規模な構成の「コレクション」クラスで「プロパティ」と呼ぶものの「セッター」関数です。

class MyPropertyCollection {
 public:
  void set_oink(double value) { oink_ = value; }
  void set_bar(int value) { bar_ = value; }
  void set_squee(bool value) { squee_ = value; }
 private:
  double oink_;
  int bar_;
  bool squee_;
};

// elsewhere
WrapperCollection wrapper_collection;  // a simple set of wrapper objects, accessed by id
MyPropertyCollection property_collection;
wrapper_collection.add(PROPERTY_OINK_ID, new Wrapper<double, MyPropertySet, &MyPropertySet::set_oink>(&property_collection);
wrapper_collection.add(PROPERTY_BAR_ID, new Wrapper<int, MyPropertySet, &MyPropertySet::set_bar>(&property_collection);
wrapper_collection.add(PROPERTY_SQUEE_ID, new Wrapper<bool, MyPropertySet, &MyPropertySet::set_squee>(&property_collection);
// +300 more
4

4 に答える 4

4
struct MyClass
{
    MyClass& Move(MyClass& m) { return *this; }
};

typedef MyClass& (MyClass::*MethodT) (MyClass&);

template< typename T >
struct ExtractType : std::false_type
{
};

template< typename R, typename C, typename A >
struct ExtractType< R (C::*)(A) >
{
    typedef C type;
};

static_assert( std::is_same< ExtractType< MethodT >::type, MyClass >::value, "oops" );

私のバージョンの gcc 4.8 で動作するようです。
コメントで述べたように機能します。これは、特殊化チェック中にコンパイラが行う「バック パターン マッチング」です。これは非常に強力です。ご覧のとおり、タイプが尊重する場合、コンパイラによってそれを構成する3つのサブタイプに分解される
ある種のパターンを指定しました: , , . 戻り値の型、クラスの型、および引数です。TRCA

ただし、1 つの引数で機能することがわかります。未定義の数の引数がある場合はどうすればよいですか?
多分チェッカークラスのリスト、または可変個のテンプレートを使用しますか?

率直に言って、これが で動作するかどうかさえ確信が持てませんvoid。テンプレートに void を配置することは常に不可能だと思います。そのため、このExtractTypeクラスの多くのバージョンで、可能な宣言のすべての組み合わせをサポートすることになります。またはそう私には思えます。

編集:

わかりましたので、これを完全にランダムに提供しますが、C++ 11 では予想よりもはるかにうまく機能するようです。gcc 4.8 では問題ありません。

struct MyClass
{
};

typedef int (MyClass::*MethodT) (bool);
typedef void (MyClass::*VV) ();
typedef void (MyClass::*IL) (int, long);

template< typename T >
struct ExtractType : std::false_type
{
};

template< typename R, typename C, class...A >
struct ExtractType< R (C::*)(A...) >
{
    typedef C type;
    typedef R returntype;
};

static_assert( std::is_same< ExtractType< MethodT >::type, MyClass >::value, "oops" );
static_assert( std::is_same< ExtractType< VV >::type, MyClass >::value, "oops" );
static_assert( std::is_same< ExtractType< IL >::type, MyClass >::value, "oops" );

static_assert( std::is_same< ExtractType< MethodT >::returntype, int >::value, "oops" );
static_assert( std::is_same< ExtractType< VV >::returntype, void >::value, "oops" );
static_assert( std::is_same< ExtractType< IL >::returntype, void >::value, "oops" );

voidクレイジーな部分は、戻り値の型を気にしないことです。もちろんC++11ですが。

于 2015-03-06T05:13:00.543 に答える
3

C++11 では、次のようなラムダを使用できます。

template <typename X, typename ARG>
std::function<void(X*, ARG)> wrapper(void (X::*mfp)(ARG))
{
    return [=](X *x, ARG arg) {
       (x->*mfp)(arg);
    };
}

VisualC++ (少なくとも VS2013 と同じくらい最近) では、[=]メンバー関数ポインターをキャプチャする (またはクラッシュが発生する) ときに値によるキャプチャを使用します。

遊び場:

#include <iostream>
#include <functional>

struct A {
    virtual void a(int i) { std::cout << "A: " << i << std::endl; }
};

struct B {
    virtual void b(int i) { std::cout << "B: " << i << std::endl; }
};

template <typename X, typename ARG>
std::function<void(X*, ARG)> wrapper(void (X::*mfp)(ARG)) {
    return [=](X *x, ARG arg) { (x->*mfp)(arg); };
}

int main()
{
    auto g = wrapper(&B::b);
    B b;
    g(&b, 3);
    auto h = wrapper(&A::a);
    A a;
    h(&a, 4);
    return 0;
}
于 2014-04-04T20:54:42.330 に答える
1

あなたがしたことを読んで、いくつかのオプションを考えさせられました:

1) インスタンス化を継承でラップします。これにより、恐ろしいものがあなたの定義に移動します。

class FooWrapper : public Wrapper< double, Foo, &Foo::set >, public Foo
{
public:
    FooWrapper() : Wrapper(this){}
};

ロジック コードは次のようになります。

  FooWrapper fooWrapper;
  fooWrapper.do_something(1.0);
  std::cout << fooWrapper.get() << std::endl;

つまり、二重のテンプレート引数を削除したのではなく、移動しただけです。

2) 1 つのレベルでラップする、より一般的な方法があります。

  template<typename argType1, class classType1>
class FooWrapper2 : public Wrapper<argType1, classType1, &classType1::set>, public classType1
{
public:
    FooWrapper2()
        : classType1(),
          Wrapper<argType1, classType1, &classType1::set>(this)
    {

    }
};

この方法には、より複雑に見えるロジックの欠点がありますが、毎回新しいラッパーを定義する必要はなく、署名ごとに新しいラッパーを定義するだけです。

  FooWrapper2<double, Foo> fooWrapper2;
  fooWrapper2.do_something(1.0);
  std::cout << fooWrapper2.get() << std::endl;

3) テンプレートのアイデアに沿って、ラッパーをラップできます。

  template<typename argType1>
class FooWrapper3 : public FooWrapper2<argType1, Foo>
{
public:
    FooWrapper3()
    {

    }
};

これからのロジックコードは少し良く見えますが、ラップするタイプごとに再サブクラス化する必要があります(テンプレートを使用するだけでなく、特定のコードを使用して):

  FooWrapper3<double> fooWrapper3;
  fooWrapper3.do_something(1.0);
  std::cout << fooWrapper3.get() << std::endl;

4) このオプションは、基本ラッパー クラスを廃止し、インターフェイスを使用します。ラッパーと同じようにインターフェイスを渡すだけで、ほとんどのアクションを実行できます。

template <typename ArgType>  
class Do_something {  
 public:  

  virtual void do_something(ArgType value) = 0;  

};  

template<typename ArgType>  
class FooWrapper4 : public Foo, public Do_something<ArgType>  
{  
public:  
    virtual void do_something(ArgType value)  
    {  
        set(1.0);  
    }  
};  

私が遊んだテストプログラム:

class Foo {
 public:
  Foo() : f_(0.0) {}
  void set(double v) { f_ = v * 2.1; }
  double get() { return f_; }
 private:
  double f_;
};


template <typename ArgType, typename ClassType, void (ClassType::*Method)(ArgType)>
class Wrapper {
 public:
  explicit Wrapper(ClassType *cls) : cls_(cls) {}

  void do_something(ArgType value) {
    (cls_->*Method)(value);
  }

 private:
  ClassType *cls_;
};


class FooWrapper : public Wrapper< double, Foo, &Foo::set >, public Foo
{
public:
    FooWrapper() : Wrapper(this){}
};


template<typename argType1, class classType1>
class FooWrapper2 : public Wrapper<argType1, classType1, &classType1::set>, public classType1
{
public:
    FooWrapper2()
        : classType1(),
          Wrapper<argType1, classType1, &classType1::set>(this)
    {

    }
};

template<typename argType1>
class FooWrapper3 : public FooWrapper2<argType1, Foo>
{
public:
    FooWrapper3()
    {

    }
};

template <typename ArgType>
class Do_something {
 public:

  virtual void do_something(ArgType value) = 0;

};

template<typename ArgType>
class FooWrapper4 : public Foo, public Do_something<ArgType>
{
public:
    virtual void do_something(ArgType value)
    {
        set(1.0);
    }
};

#include <iostream>
int main(int argc, char ** argv) {
  Foo foo;
  Wrapper<double, Foo, &Foo::set> wrapper(&foo);

  wrapper.do_something(1.0);
  std::cout << foo.get() << std::endl;

  FooWrapper fooWrapper;
  fooWrapper.do_something(1.0);
  std::cout << fooWrapper.get() << std::endl;
  // outputs "2.1"

  FooWrapper2<double, Foo> fooWrapper2;
  fooWrapper2.do_something(1.0);
  std::cout << fooWrapper2.get() << std::endl;

  FooWrapper3<double> fooWrapper3;
  fooWrapper3.do_something(1.0);
  std::cout << fooWrapper3.get() << std::endl;

  FooWrapper4<double> fooWrapper4;
  fooWrapper4.do_something(1.0);
  std::cout << fooWrapper4.get() << std::endl;

  return 0;
}
于 2013-02-09T02:39:03.910 に答える
1

これは、 C++11 の構成要素である::std::mem_fn+の不適切な再実装です。::std::bindこれらを使用してこれを行う方法は次のとおりです。

#include <functional>

int main() {
   Foo foo;
   auto wrapper = ::std::bind(::std::mem_fn(&Foo::set), ::std::ref(foo), _1);
   wrapper(5); // Calls foo.set(5)
}

しかし、もちろん、C++03 ソリューションが必要です。Boost を使用すると、C++03 でこれを取得できます。また、TR1 を使用した C++03 では、このようなことが可能であると考えています。機能するかどうかを確認することで、それがあるかどうかを判断できます#include <tr1/functional>。TR1 がある場合、それらは::std::tr1名前空間に表示されます。

さて、そうでない方法が 1 つあります。関数ポインター自体をクラスの型シグネチャの一部にしました。それは奇妙なことですが、すでにご存じのとおり、確かに可能です。ただし、コンパイル時にClassTypeとの値を決定できるようにするのは難しいことです。ArgTypeテンプレート関数の引数の一致でそれを行うことができますが、C++03 にはauto.

于 2013-02-09T01:59:47.430 に答える