簡體   English   中英

C ++ - 是否可以從模板中的成員函數類型中提取類和參數類型?

[英]C++ - is it possible to extract class and argument types from a member function type in a template?

我想用模板化的類包裝符合'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”被指定了兩次 - 這里看起來多余。

所以我想知道的是,是否可以避免使用模板參數ClassType 例如,如果可以從成員函數指針參數中暗示或提取它,那么就不需要在Wrapper <>的實例化中明確指定它。

以類似的方式,避免顯式指定ArgType也是有用的,因為(也許)它可以從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 ++ 03作為要求,而不是C ++ 11,但也有興趣知道C ++ 11可能提供什么。

編輯:更多信息 - 我打算使用這個機制來包裝~300個成員函數(都屬於ClassType,或一組非常相似的類),但只有大約六個左右的簽名需要考慮:

  • void(ClassType :: Function)(ArgType) - 其中ArgType是'浮動'
  • void(ClassType :: Function)(ArgType) - 其中ArgType是'integral'
  • void(ClassType :: Function)(bool)
  • void(ClassType :: Function)(IndexType,ArgType) - 上面三個帶有額外的'index'參數

例如,成員函數是我在大型配置'集合'類中稱為“屬性”的“setter”函數(而不是上面的簡單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
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版本。
它的工作方式與我在評論中提到的一樣,它是編譯器在專業化檢查期間執行的“后向模式匹配”。 這非常強大。
所以你看,我們指定了某種模式,如果類型T尊重,它將被編譯器分解為組成它的三個子類型: RCA 哪個是返回類型,類類型和參數。

但是你可以看到它適用於一個參數。 當我們有一個未定義的參數時怎么辦?
也許是一個檢查器類列表,或使用可變參數模板?

坦率地說,坦率地說,我甚至不確定這會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然而。

在C ++ 11中,您可以使用lambdas,例如:

 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; } 

這是一個糟糕的重新實現::std::mem_fn + ::std::bind ,它們是C ++ 11結構。 以下是使用這些方法的方法:

#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>有效來判斷您是否擁有該#include <tr1/functional> 如果你有TR1那些出現在::std::tr1命名空間中。

現在,有一種方式不是。 您已將函數指針本身作為類的類型簽名的一部分。 這有點奇怪,但你可能已經知道了。 能夠在編譯時確定ClassTypeArgType值是很棘手的。 您可以使用模板函數參數匹配來完成它,但沒有用,因為C ++ 03沒有auto

閱讀你所做的事情讓我想到了幾個選擇:

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)在一個層面上有一種更通用的方式來包裝它:

  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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM