2010-07-09 6 views
3

Я пытаюсь написать код, который вызывает метод класса, заданный как параметр шаблона. Чтобы упростить, вы можете предположить, что метод имеет единственный параметр (произвольного типа) и возвращает void. Цель состоит в том, чтобы избежать шаблона в вызывающем сайте, не набрав тип параметра. Вот пример кода:Как передать указатель метода в качестве параметра шаблона

template <class Method> class WrapMethod { 
    public: 
    template <class Object> 
    Param* getParam() { return &param_; } 
    Run(Object* obj) { (object->*method_)(param_); } 
    private: 
    typedef typename boost::mpl::at_c<boost::function_types::parameter_types<Method>, 1>::type Param; 
    Method method_; 
    Param param_ 
}; 

Сейчас на сайте вызова, я могу использовать метод никогда не писать тип параметра.

Foo foo; 
WrapMethod<BOOST_TYPEOF(&Foo::Bar)> foo_bar; 
foo_bar.GetParam()->FillWithSomething(); 
foo_bar.Run(foo); 

Таким образом, этот код работает, и это почти то, что я хочу. Единственная проблема заключается в том, что я хочу избавиться от макрокоманды BOOST_TYPEOF на вызывающем сайте. Я хотел был бы написать что-то вроде WrapMethod<Foo::Bar> foo_bar вместо WrapMethod<BOOST_TYPEOF(&Foo::Bar)> foo_bar.

Я подозреваю, что это невозможно, так как нет способа ссылаться на подпись метода, отличную от использования самой подписи метода (которая является переменной для WrapMethod и что-то довольно большое для ввода на вызывающем сайте) или получения указатель метода, а затем выполните typeof.

Любые подсказки о том, как исправить эти или различные подходы к тому, как избежать ввода типа параметра на вызывающем сайте, оцениваются.

Для уточнения моих потребностей: решение не должно иметь имя типа Param в вызывающем сайте. Кроме того, он не может вызывать FillWithSomething из WrapMethod (или аналогичного). Поскольку имя этого метода может изменяться от типа Param до типа Param, оно должно проживать на вызывающем сайте. Решение, которое я дал, удовлетворяет обоим этим ограничениям, но нуждается в уродливом BOOST_TYPEOF в вызывающем сайте (использование его внутри WrapMethod или другой косвенности было бы хорошо, так как это код, который мои пользователи api не будут видеть, пока это будет правильно).

Ответ:

Насколько я могу сказать, что нет возможных решений. Это сводится к тому, что невозможно написать что-то вроде WrapMethod<&Foo::Bar>, если подпись Бар не известна заранее, хотя требуется только мощность. В более общем плане вы не можете иметь параметры шаблона, которые принимают значения (а не типы), если тип не фиксирован. Например, невозможно написать что-то вроде typeof_literal<0>::type, которое вычисляет int и typeof_literal<&Foo::Bar>::type, что в моем примере будет оцениваться void (Foo*::)(Param). Обратите внимание, что ни BOOST_TYPEOF, ни declype не помогли бы, потому что они должны были жить на сайте катания и не могли быть заглублены глубже в коде. Легитимный, но недопустимый синтаксис ниже будет решить эту проблему:

template <template<class T> T value> struct typeof_literal { 
    typedef decltype(T) type; 
}; 

В C++ 0x, как указано в выбранной реакцию (и в других, используя BOOST_AUTO), можно использовать автоматическое ключевое слово для достижения тех же целей в другим способом:

template <class T> WrapMethod<T> GetWrapMethod(T) { return WrapMethod<T>(); } 
auto foo_bar = GetWrapMethod(&Foo::Bar); 

ответ

2

Если ваш компилятор поддерживает decltype, используйте decltype:

WrapMethod<decltype(&Foo::Bar)> foo_bar; 

EDIT: или, если вы действительно хотите сохранить набор текста и иметь C++ 0x совместимый компилятор с:

template <class T> WrapMethod<T> GetWrapMethod(T) { return WrapMethod<T>(); } 
auto foo_bar= GetWrapMethod(&Foo::Bar); 

edit2 : Хотя, действительно, если вы хотите, чтобы он выглядел красиво, вам либо нужно подвергать пользователей сложностям языка C++, либо обернуть их самим в макропроцессоре препроцессора:

#define WrapMethodBlah(func) WrapMethod<decltype(func)> 
+0

Это работает, но BOOST_TYPEOF - тип decltype одинаковый (почти). Использование их в WrapMethod выполняется нормально, но не на вызывающем сайте. – Davi

+0

Справа. Думаю, у вашего отредактированного ответа есть почти все варианты. Итак, с C++ 03 я застрял, и с C++ 0X я мог использовать decltype вместо BOOT_TYPEOF, или я мог бы использовать автоматическое решение, что довольно приятно. – Davi

0

Вы рассмотрели использование шаблонов методов?

template <typename T> void method(T & param) 
{ 
    //body 
} 

Теперь компилятор может неявно определить тип параметра

  int i; 
      bool b; 

      method(i); 
      method(b); 

Или вы можете предоставить тип явно

  method<int>(i); 

Вы можете предоставить специализации для различных типов данных

  template <> void method<int>(int param) 
      { 
       //body 
      } 
+0

Потому что он хочет хранить их как элемент данных. – Puppy

+0

Как шаблоны членов должны помочь с объявлением 'WrapMethod'? –

3

Написать как:

template <typename Object, typename Param, void (Object::*F)(Param)> 
class WrapMethod { 
public: 
    Param* getParam() { return &param_; } 
    void Run(Object* obj) { (obj->*F)(param_); } 

private: 
    Param param_; 
}; 

и

Foo foo; 
WrapMethod<Foo, Param, &Foo::Bar> foo_bar;  
foo_bar.getParam()->FillWithSomething(); 
foo_bar.Run(foo); 

EDIT: Показана шаблонная функция, позволяющую делать то же самое, без каких-либо специальных оберток шаблона:

template <typename Foo, typename Param> 
void call(Foo& obj, void (Foo::*f)(Param)) 
{ 
    Param param; 
    param.FillWithSomthing(); 
    obj.*f(param); 
} 

и использование это как:

Foo foo; 
call(foo, &Foo::Bar); 

второй EDIT: Изменение функции шаблона, чтобы взять функцию инициализации в качестве параметра, а также:

template <typename Foo, typename Param> 
void call(Foo& obj, void (Foo::*f)(Param), void (Param::*init)()) 
{ 
    Param param; 
    param.*init(); 
    obj.*f(param); 
} 

и использовать его как:

Foo foo; 
call(foo, &Foo::Bar, &Param::FillWithSomething); 
+1

Все дело в том, что на вызывающем сайте не появляется параметр Param. Таким образом, проблема с вашим решением заключается в том, что я должен набирать WrapMethod foo_bar (см., Что мне нужно было ввести Param), и этого я и хочу избежать. – Davi

+0

Вы не сказали этого в своем первоначальном вопросе. В любом случае, с сегодняшним C++ невозможно сделать иначе, либо используйте typeof, либо используйте предоставленное мной решение. Но, если вы были готовы изменить свой дизайн, чтобы использовать функцию шаблона (вместо шаблона класса), это можно сделать. Я отредактировал свой ответ, чтобы проиллюстрировать, что я имею в виду. – hkaiser

+0

Ах, спасибо за второй вариант, но он все равно не помогает. Вызов param.FillWithSomething() должен жить на вызывающем сайте, так как этот метод может отличаться для разных параметров Param. Я думаю, что это невозможно с помощью C++ 0X (хотя с авто можно использовать функцию шаблона). – Davi

0

Когда вы уже позволяет BOOST_TYEPOF() , рассмотрите возможность использования BOOST_AUTO() с функцией генератора объектов для разрешения вычитания типа:

template<class Method> WrapMethod<Method> makeWrapMethod(Method mfp) { 
    return WrapMethod<Method>(mfp); 
} 

BOOST_AUTO(foo_bar, makeWrapMethod(&Foo::Bar)); 
+0

Это работает, но не лучше, чем решение, которое я разместил. BOOST_AUTO вызывает BOOST_TYPEOF под капотами. Я хочу избежать вызова макроса на вызывающем сайте. Выполнение грязных трюков внутри WrapMethod в порядке, но вызывающий сайт должен быть доступен для чтения. – Davi

+0

@Davi: Если вам не нравятся макросы, он не становится лучше pre-C++ 0x (в C++ 0x у вас есть 'auto' и' decltype'). Невозможно иметь аргументы шаблона типа non-type с произвольным типом для шаблонов классов, поэтому невозможно только передать указатель на функцию-член ... Какие бы трюки могли выполнять вспомогательные функции, это оставляет вам проблему указания тип переменной. –

+0

Если макросов следует избегать, и вы не можете использовать упомянутые функции C++ 0x в своем коде, наиболее читаемой альтернативой может быть стиль Boost.Function 'WrapMethod '. –

0

Хорошо, давайте пойдем на это.

Прежде всего, обратите внимание на то, что параметр параметра шаблона доступен (как указано в нескольких ответах) с функциями.

Итак, вот реализация (вроде):

// WARNING: no virtual destructor, memory leaks, etc... 

struct Foo 
{ 
    void func(int e) { std::cout << e << std::endl; } 
}; 

template <class Object> 
struct Wrapper 
{ 
    virtual void Run(Object& o) = 0; 
}; 

template <class Object, class Param> 
struct Wrap: Wrapper<Object> 
{ 
    typedef void (Object::*member_function)(Param); 

    Wrap(member_function func, Param param): mFunction(func), mParam(param) {} 

    member_function mFunction; 
    Param mParam; 

    virtual void Run(Object& o) { (o.*mFunction)(mParam); } 
}; 

template <class Object, class Param> 
Wrap<Object,Param>* makeWrapper(void (Object::*func)(Param), Param p = Param()) 
{ 
    return new Wrap<Object,Param>(func, p); 
} 


int main(int argc, char* argv[]) 
{ 
    Foo foo; 
    Wrap<Foo,int>* fooW = makeWrapper(&Foo::func); 
    fooW->mParam = 1; 
    fooW->Run(foo); 

    Wrapper<Foo>* fooW2 = makeWrapper(&Foo::func, 1); 
    fooW2->Run(foo); 
    return 0; 
} 

Я думаю, что с помощью базового класса является родным C++ способом сокрытия информации по типу стирания.

+0

Но это ограничило бы параметр как связанный во время создания оболочки, так и с использованием некоторого виртуального базового класса ... :) –

+0

Да, я согласен, это альтернативный подход. Я хотел бы знать, что такое вариант использования, потому что здесь нам не хватает данных. Я чувствую, что базовый класс будет необходим в той или иной точке, потому что, если не для полиморфизма, нет смысла обтекать объект вместо традиционного шаблона метода. –

+0

Да, интересный подход, но, как говорит Георг, вам либо нужно ввести «int» в вызывающем сайте (первый пример), либо вам нужно иметь анонимный экземпляр (пример 2). Я просто не могу выполнить свою работу с FillWithSomething(). – Davi

Смежные вопросы