C ++ - 是否可以从模板中的成员函数类型中提取类和参数类型?

时间:2013-02-09 01:15:14

标签: c++ templates template-meta-programming member-function-pointers

我想用模板化的类包装符合'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&lt;&gt;的实例化“Foo”指定了两次 - 这里看起来多余。

所以我想知道的是,是否可以避免模板参数 ClassType 。例如,如果可以从成员函数指针参数中暗示或提取它,那么就不需要在Wrapper&lt;&gt;的实例化中明确指定它。

以类似的方式,避免明确指定 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为'floating'
  • 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

4 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:3)

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版本 它的作用就像我在评论中提到的那样,它的背面图案匹配&#34;编译器在专业化检查期间执行的操作。这非常强大。
所以你看,我们指定了一种模式,如果类型T尊重,它将由编译器分解为组成它的三个子类型:RC,{{ 1}}。哪个是返回类型,类类型和参数。

但是你可以看到它适用于一个参数。当我们有一个未定义的参数时怎么办?
可能是一个检查器类列表,或使用可变参数模板?

坦率地说,坦率地说,我甚至不确定这会与A一起使用。我认为void总是不可能放在模板中,因此它会导致这个void类的许多版本支持所有可能的声明组合。或者在我看来。

编辑:

好的,所以我完全随机地放弃了它,但是在C ++ 11中它看起来比我预期的要好得多,这在gcc 4.8上是可以的:

ExtractType

疯狂的部分是它在回归类型中并不介意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" ); 。当然是它的C ++ 11。

答案 2 :(得分:1)

这是::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>是否有效来判断您是否拥有此功能。如果你有TR1那些出现在::std::tr1名称空间。

现在,有一种方法不是。您已将函数指针本身作为类的类型签名的一部分。这有点奇怪,但你可能已经知道了。能够在编译时确定ClassTypeArgType值是很棘手的。您可以使用模板函数参数匹配来完成它,但没有用,因为C ++ 03没有auto

答案 3 :(得分: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)在一个层面上有一种更通用的方式来包装它:

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