2017-02-12 2 views
4

Есть несколько вопросов о SO, которые адресуют указатели на функции в качестве параметров/аргументов (here, here, here и т. Д.). На самом деле, я попросил related question на днях. Однако этот вопрос немного отличается.Хранение указателя функции члена из произвольного класса как переменной экземпляра класса

Моя проблема в том, что я пишу класс, который я хочу быть чрезвычайно гибким.

То, что у меня сейчас работает для функций, не являющихся членами. Он размещен ниже

template <typename T> 
class MyClass 
{ 
    private: 
     typedef double (*firstFunctionPtr) (const T &var); 

     typedef bool (*secondFunctionPtr)(const T &var); 

     // Function pointers as member variables 
     firstFunctionPtr _firstFunc; 
     secondFunctionPtr _secondFunc; 

    public: 
     inline MyClass(firstFunctionPtr firstFunc, 
      secondFunctionPtr secondFunc); 
}; 

template<typename T> 
MyClass<T>::MyClass(firstFunctionPtr firstFunc, secondFunctionPtr secondFunc) : 
    _firstFunc(firstFunc), 
    _secondFunc(secondFunc), 
    {} 

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

This answer предполагает, что

В правильном интерфейсе C++ вы можете захотеть взглянуть на имеющих ваши функции принимают шаблонный аргумент для функциональных объектов для использования произвольных типов классов.

Однако, я не смог составить эту компиляцию. Я пробовал шаблонировать мои typedefs (используя подход сглаживания C++ 11), и я попытался добавить второй параметр шаблона в класс для обработки вызывающего класса этих функций-членов, но ни один из них не сработал.

This Q/A, похоже, приближается к тому, что я пытаюсь сделать, но я не могу сделать головы или хвосты.

  1. Может ли кто-нибудь объяснить, как я могу изменить свой класс, чтобы обрабатывать передаваемые переменные произвольных функций?
  2. Кроме того, возможно ли это сделать так, чтобы он мог обрабатывать либо произвольные функции-члены, либо функции, не являющиеся членами?
  3. Наконец, можно ли это сделать с помощью шаблонов?

Для записи, я стараюсь избегать использования заголовка functional, но это может быть поручение дурака не использовать его.

+0

Проблема с «указателем функции на произвольный класс» заключается в том, что вам нужен «объект произвольного класса» для его использования. И нет, 'void *' не будет работать. Вероятно, вы закончите с 'std :: bind' и' std :: function', потому что у этого больше нет зависимости от бесконечного множества произвольных классов. – MSalters

ответ

2

Если вы хотите MyClass быть шаблон, который может содержать как свободную функцию указателей типов:

double (*)(const T &var); 
bool (*)(const T &var); 

для некоторых типов параметров T, или в качестве альтернативы члена-функции указателей типов:

double (C::*)(const T &var); 
bool (C::*)(const T &var); 

для некоторых типов параметров C и T то, MyClass должны быть настроены параметры обоими T и C и вам потребуется две специализации:

  1. Где C некоторый тип неклассовых
  2. Где C любого типа

класс в случае (1), тип non-class C не может иметь функции-члены, , чтобы реализовать специализацию указателя свободной функции.

В случае (2) класс C может быть таким, который имеет функции-члены, так что один будет реализовывать специализацию указателя-функции-члена.

Очевидным выбором для неклассового типа C является void. Таким образом, мы можем сделать C по умолчанию void:

Первичный шаблон

template<typename T, typename C = void> 
struct MyClass; 

Так что:

MyClass<T> 

будет свободный указатель функции специализации для T и:

MyClass<T,C> 

для любых C кроме void, будет специалистом-указателем-членом.

Как вы знаете, вы можете использовать std::enable_if и SFINAE сделать компилятор выбрал одну специализацию шаблона класса или другой, в зависимости от параметров ли один его шаблон U удовлетворяет некоторые испытания compiletime.Вы можете взять такой подход здесь, а другой один доступен, не требует, чтобы устройство:

Начиная с основным шаблоном, мы просто хотели бы иметь:

Бесплатной функция специализация

template<typename T> 
struct MyClass<T> 
{ 
    ... for free function pointers ... 
}; 

и:

функция член специализация

template<typename T, typename C> 
struct MyClass<T,C> 
{ 
    ... for member function pointers ... 
}; 

Но у нас не может быть только этого, потому что функция «специализация»-члена имеет ровно те же параметры шаблона, что и основной шаблон. Это означает, что не специализация, и компилятор не допустит этого.

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

Новый первичный шаблон

template <typename T, typename C = void, typename Default = void> 
struct MyClass; 

Так вот иллюстративное решение:

// Primary template 
template <typename T, typename C = void, typename Default = void> 
struct MyClass; 

// Free function specialization 
template <typename T> 
struct MyClass<T> 
{ 
    using firstFunctor_t = double(*)(T const &); 
    using secondFunctor_t = bool(*)(T const &); 

    MyClass(firstFunctor_t firstFunc, secondFunctor_t secondFunc) 
    : _firstFunc(firstFunc), 
     _secondFunc(secondFunc) 
    {} 

    double callFirst(T const & var) { 
     return _firstFunc(var); 
    } 

    bool callSecond(T const & var) { 
     return _secondFunc(var); 
    } 

private: 

    firstFunctor_t _firstFunc; 
    secondFunctor_t _secondFunc; 
}; 

// Member function specialization 
template <typename T, typename C> 
struct MyClass<T,C> 
{ 
    using firstFunctor_t = double(C::*)(T const &); 
    using secondFunctor_t = bool(C::*)(T const &) const; 

    MyClass(firstFunctor_t firstFunc, secondFunctor_t secondFunc) 
    : _firstFunc(firstFunc), 
     _secondFunc(secondFunc) 
    {} 

    double callFirst(C & obj, T const & var) { 
     return (obj.*_firstFunc)(var); 
    } 

    double callFirst(C const & obj, T const & var) { 
     auto & o = const_cast<C&>(obj); 
     return (o.*_firstFunc)(var); 
    } 

    bool callSecond(C & obj, T const & var) { 
     return (obj.*_secondFunc)(var); 
    } 

    bool callSecond(C const & obj, T const & var) { 
     auto & o = const_cast<C&>(obj); 
     return (o.*_secondFunc)(var); 
    } 

private: 

    firstFunctor_t _firstFunc; 
    secondFunctor_t _secondFunc; 
}; 

В функции специализации членов, обратите внимание на несколько моментов, которые вы могли бы не рассматривали: -

Я решил, что вторая функция-член, которую я хочу сохранить, должна быть описание товара const участник функция. Более чем вероятно, что функция-член C , которая принимает аргумент T const & и возвращает bool, будет членом const , не правда ли? И если да, то, что const-ness должен быть частью определения типа член-функция, которую я использую в специализации:

using secondFunctor_t = bool(C::*)(T const &) const; 

или пытается создать экземпляр специализации с любой bool (C::*)(T const &) const не будет компилироваться.

Кроме того, я предоставил две перегрузки для каждого из MyClass<T,C>::callFirst и MyClass<T,C>::callSecond, один с аргументами:

C & obj, T const & var 

, а другой с аргументами:

C const & obj, T const & var 

Без второй попытки вызвать либо MyClass<T,C>::callFirst или MyClass<T,C>::callSecond с obj, который будет const, не будет скомпилировать.

Для программы демонстрационного этого решения, которое вы можете добавить:

#include <iostream> 
#include <string> 

double foo(std::string const & s) 
{ 
    return std::stod(s); 
} 

bool bar(std::string const & s) 
{ 
    return s.size() > 0; 
} 

struct SomeClass 
{ 
    SomeClass(){}; 
    double foo(std::string const & s) { 
     return ::foo(s); 
    } 

    bool bar(std::string const & s) const { 
     return ::bar(s); 
    } 
}; 

int main() 
{ 
    MyClass<std::string> my0{foo,bar}; 
    std::cout << std::boolalpha; 
    std::cout << my0.callFirst("1.11") << std::endl; 
    std::cout << my0.callSecond("Hello World") << std::endl; 

    MyClass<std::string,SomeClass> my1{&SomeClass::foo,&SomeClass::bar}; 
    SomeClass thing; 
    std::cout << my1.callFirst(thing,"2.22") << std::endl; 
    std::cout << my1.callSecond(thing,"Hello World") << std::endl; 

    SomeClass const constThing; 
    std::cout << my1.callFirst(constThing,"3.33") << std::endl; 
    std::cout << my1.callSecond(constThing,"Hello World") << std::endl; 
    return 0; 
} 

See it live

Вы сказали, что вы хотите этот шаблон, чтобы быть «чрезвычайно гибким». Иллюстративное решение установлено на ваш пример, но вы можете быть заинтересованы в том, чтобы знать, что это не почти так же гибко, как вы могли бы получить. . Для бесплатных функций и функций-членов, с дополнительными параметрами variadic template , ваш шаблон может хранить и вызывать функции [member] с условными типами возврата и произвольными числами аргументов произвольных типов. См. this question и ответ.

+0

Это тот ответ, на который я надеялся. Благодаря! Один (субъективный) вопрос. Неправильная практика имеет посторонние шаблонные параметры? Похоже, он просит проблемы иметь целенаправленную неиспользованную информацию. – marcman

+0

@marcman Hi. Нет, это не плохая практика; это биржевая торговля для TMP. Посмотрите (конец примеров), как [std :: enable_if] (http://en.cppreference.com/w/cpp/types/enable_if) используется для включения SFINAE-выбора между специализациями шаблонов классов. * Что он делает * добавляет параметр шаблона по умолчанию к основному шаблону , который не имеет никакой пользы в какой-либо специализации, но позволяет нам контроль *, который * специализация компилятор будет создавать в любом контексте. Тоже самое. –

0

Все, что вам нужно сделать, это bind экземпляр объекта для указателя функции-члена в качестве первого аргумента.

struct foo { 
    float bar1(const type &var); 
    bool bar2(const type &var); 
}; 
foo my_foo; 
auto f1 = std::bind(&foo::bar1, my_foo, _1); 
auto f2 = std::bind(&foo::bar2, my_foo, _1); 
MyClass<type> my_obj(f1, f2); 
+0

Но для работы с моими переменными-членами мне нужен тип шаблона, а не аргумент. Как это изменит ваш ответ? – marcman

+0

Хорошо, просто добавьте это. Это отвечает на ваш OP о том, чтобы указать указатели на функции из функций-членов. –

+0

Ну, подождите. Итак, 'auto f1 = std :: bind (& foo :: bar1, my_foo); auto f2 = std :: bind (& foo :: bar2, my_foo); 'должен быть в состоянии быть сохранен в качестве членов, объявленных в моем OP? Вернее, эти утверждения позволят указывать указатели на функции-члены как указатели на функции, не являющиеся членами? – marcman

1

Я sugest создать вспомогательный объект, который будет хранить тип вы хотите работать с:

template <typename RETURN, typename TYPE, typename CLASS> 
struct function_pointer 
{ using type_t = RETURN (CLASS::*)(const TYPE &); }; 

template <typename RETURN, typename TYPE> 
struct function_pointer<RETURN, TYPE, std::nullptr_t> 
{ using type_t = RETURN (*)(const TYPE &); }; 

Этот тип создаст член-функцию-указатель, если класс предусмотрен в качестве третьего параметра и указатель функции в противном случае. Теперь мы можем использовать этот хелпер в MyClass:

template <typename T, typename CLASS = std::nullptr_t> 
class MyClass 
{ 
    using firstFunctionPtr = typename function_pointer<double, T, CLASS>::type_t; 
    using secondFunctionPtr = typename function_pointer<bool, T, CLASS>::type_t; 

    // Function pointers as member variables 
    firstFunctionPtr _firstFunc; 
    secondFunctionPtr _secondFunc; 

public: 
    inline MyClass(firstFunctionPtr firstFunc, secondFunctionPtr secondFunc) : 
     _firstFunc(firstFunc), 
     _secondFunc(secondFunc) 
    {} 

    void call_first(CLASS &c, const T&v) { (c.*_firstFunc)(v); } 
    void call_second(CLASS &c, const T&v) { (c.*_secondFunc)(v); } 

    void call_first(const T&v) { (_firstFunc)(v); } 
    void call_second(const T&v) { (_secondFunc)(v); } 
}; 

Я добавил call_* функции просто, чтобы показать случай использования, который будет, как показано ниже:

// Some class with the expected function signatures 
struct S1 
{ 
    int i = 0; 
    double d(const int &) { std::cout << i << ' ' << __PRETTY_FUNCTION__ << '\n'; return{}; } 
    bool b(const int &) { std::cout << i << ' ' << __PRETTY_FUNCTION__ << '\n';  return{}; } 
}; 

// Another class with the expected function signatures 
struct S2 
{ 
    double d(const int &) { std::cout << __PRETTY_FUNCTION__ << '\n'; return{}; } 
    bool b(const int &) { std::cout << __PRETTY_FUNCTION__ << '\n'; return{}; } 
}; 

// Free function with which could have the expected function signature 
template <typename R> 
R f(const int &) { std::cout << __PRETTY_FUNCTION__ << '\n'; return{}; } 

Использование MyClass с произвольным классом (S1):

S1 a{1}, b{2}; 
S2 c, d; 
MyClass<int, S1> MCiS1(&S1::d, &S1::b); 
MCiS1.call_first(a, 111); // Prints -> 1 double S1::d(const int&) 
MCiS1.call_second(b, 222); // Prints -> 2 bool S1::b(const int&) 
MCiS1.call_first(c, 111); // Error decltype(c) is not S1. 
MCiS1.call_second(d, 222); // Error decltype(d) is not S1. 

Использование MyClass с другим классом (S2):

MyClass<int, S2> MCiS2(&S2::d, &S2::b); 
MCiS2.call_first(c, 111); // Prints -> double S2::d(const int&) 
MCiS2.call_second(d, 222); // Prints -> bool S2::b(const int&) 
MCiS2.call_first(a, 111); // Error decltype(c) is not S2. 
MCiS2.call_second(b, 222); // Error decltype(d) is not S2. 

Использование MyClass с функциями, не являющимися членами:

MyClass<int> MCi(f<double>, f<bool>); 
MCi.call_first(111); // Prints -> R f(const int&) [with R = double] 
MCi.call_second(222); // Prints -> R f(const int&) [with R = bool] 

Проверить демо Here.

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