且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-10-22 13:50:29

struct MyClass{我的课堂&Move(MyClass& m) { return *this;}};typedef MyClass&(MyClass::*MethodT) (MyClass&);模板结构提取类型:std::false_type{};模板结构体提取类型{typedef C 类型;};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);模板结构提取类型:std::false_type{};模板结构体提取类型{typedef C 类型;typedef R 返回类型;};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.

I would like to wrap member functions that conform to the type 'void (ClassType::Function)(ArgType)' with a templated class. Later, I want to pass an instance of ClassType to an instance of this template and have it invoke the wrapped method:

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

Notice in the instantiation of Wrapper<> that "Foo" is specified twice - it looks redundant here.

So what I'd like to know is whether it's possible to avoid the template parameter ClassType. For instance, if it is possible to imply or extract it from the member function pointer parameter, then it wouldn't need to be explicitly specified in the instantiation of Wrapper<>.

In a similar manner, it would be useful to avoid explicitly specifying ArgType also, as (perhaps) it can be determined from Foo::set?

Is this possible in C++? Perhaps something along these (entirely fantastical) lines:

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);
  // ...
}

Or, perhaps there's another level of template magic that can be invoked that would do something along these lines:

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

I'm interested to know what mechanisms might be available, if any.

I'm using C++03 as a requirement, not C++11, but also interested to know what C++11 might offer.

EDIT: more info - I intend to use this mechanism to wrap ~300 member functions (all belonging to ClassType, or a set of very similar classes), but there will only be around six or so signatures to consider:

  • void (ClassType::Function)(ArgType) - where ArgType is 'floating'
  • void (ClassType::Function)(ArgType) - where ArgType is 'integral'
  • void (ClassType::Function)(bool)
  • void (ClassType::Function)(IndexType, ArgType) - the above three with an extra 'index' argument

The member functions are 'setter' functions for what I call "properties" in a large configuration 'collection' class, for example (rather than the simple Foo above):

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" );

It appears to work in my version of gcc 4.8.
It works like I mentioned in the comment, its a "back pattern matching" that the compiler does during specialization checks. This is very powerful.
So you see, we specified some kind of pattern that if the type T respects, it will be decomposed by the compiler into the three subtypes that composes it: R, C, A. Which is return type, class type and argument.

However you can see that it works with one argument. How to do when we have an undefined number of arguments ?
Maybe a list of checker classes, or use variadic templates ?

And frankly in all honesty, I am not even sure this will work with void. I think void is always impossible to place in template, therefore it will result in many versions of this ExtractType class to support all combinations of possible declarations. Or so it seems to me.

EDIT:

Ok so I'm giving this away completely randomly, but it seems in C++11 it works much better than I have expected, this is ok on 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" );

The crazy part being that it doesn't mind void in the return type. Of course its C++11 however.