[英]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,或一組非常相似的類),但只有大約六個左右的簽名需要考慮:
例如,成員函數是我在大型配置'集合'類中稱為“屬性”的“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
尊重,它將被編譯器分解為組成它的三個子類型: 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然而。
在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
命名空間中。
現在,有一種方式不是。 您已將函數指針本身作為類的類型簽名的一部分。 這有點奇怪,但你可能已經知道了。 能夠在編譯時確定ClassType
和ArgType
值是很棘手的。 您可以使用模板函數參數匹配來完成它,但沒有用,因為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.