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”被指定了两次——在这里看起来是多余的。

所以我想知道的是是否可以避免模板参数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 是“积分”
  • void (ClassType::Function)(bool)
  • void (ClassType::Function)(IndexType, ArgType) - 上面三个带有额外的“索引”参数

例如,成员函数是我在大型配置“集合”类中称为“属性”的“设置器”函数(而不是上面的简单 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 版本。
它就像我在评论中提到的那样工作,它是编译器在专业化检查期间执行的“反向模式匹配”。这是非常强大的。
所以你看,我们指定了某种模式,如果类型T尊重,编译器将把它分解成组成它的三个子类型:R, C, A。即返回类型、类类型和参数。

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

老实说,我什至不确定这是否适用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 中,您可能会使用 lambda,例如:

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

::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命名空间中。

现在,有一种方法不是。您已将函数指针本身作为类的类型签名的一部分。这是一件很奇怪的事情,但正如你已经知道的那样,这肯定是可能的。但是,能够在编译时确定ClassTypeandArgType值是很棘手的。您可以使用模板函数参数匹配来做到这一点,但没有用处,因为 C++03 没有auto.

于 2013-02-09T01:59:47.430 回答
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;
}
于 2013-02-09T02:39:03.910 回答