Convert std:: bind to function pointer
У меня есть сторонняя библиотека, у которой есть метод, который принимает указатель на функцию в качестве первого параметра:
int third_party_method(void (*func)(double*, double*, int, int, double*), ...);
Я хочу передать указатель на метод класса, который объявлен следующим образом:
class TestClass
{
public:
void myFunction (double*, double*, int, int, void*);
Я попытался передать эту функцию следующим образом:
TestClass* tc = new TestClass();
using namespace std::placeholders;
third_party_method(std::bind(&TestClass::myFunction, tc, _1, _2, _3, _4, _5), ...);
Однако это не скомпилируется:
Conversion of parameter 1 from 'std::tr1::_Bind<_Result_type,_Ret,_BindN>' to 'void (__cdecl *)(double *,double *,int,int,void *)' is not possible
with
[
_Result_type=void,
_Ret=void,
_BindN=std::tr1::_Bind6<std::tr1::_Callable_pmf<void (__thiscall TestClass::* const )(double *,double *,int,int,void *),TestClass,false>,TestClass *,std::tr1::_Ph<1>,std::tr1::_Ph<2>,std::tr1::_Ph<3>,std::tr1::_Ph<4>,std::tr1::_Ph<5>>
]
Есть ли способ передать член функции?
Ответы
Ответ 1
Есть ли способ передать член функции?
Если ваш объект класса не является каким-то глобальным объектом, это невозможно. Поскольку объекты могут содержать некоторые данные, в то время как указатель на функцию - это просто указатель на функцию - он не содержит контекста времени выполнения, а только время компиляции.
Если вы принимаете уникальные идентификаторы времени компиляции для каждой передачи обратного вызова, вы можете использовать следующий обобщенный подход.
Использование:
void test(void (*fptr)())
{
fptr();
}
struct SomeStruct
{
int data;
void some_method()
{
cout << data << endl;
}
void another_method()
{
cout << -data << endl;
}
};
int main()
{
SomeStruct local[] = { {11}, {22}, {33} };
test(get_wrapper<0>( boost::bind(&SomeStruct::some_method,local[0]) ));
test(get_wrapper<1>( boost::bind(&SomeStruct::another_method,local[0]) ));
test(get_wrapper<2>( boost::bind(&SomeStruct::some_method,local[1]) ));
test(get_wrapper<3>( boost::bind(&SomeStruct::another_method,local[1]) ));
test(get_wrapper<4>( boost::bind(&SomeStruct::some_method,local[2]) ));
test(get_wrapper<5>( boost::bind(&SomeStruct::another_method,local[2]) ));
}
Он может не требовать уникального идентификатора для каждого вызова, например, потому что у функторов уже есть разные типы, или область выполнения их использования не перекрывается. Но безопаснее использовать уникальный идентификатор каждый раз.
Реализация:
живая демонстрация
#include <boost/optional.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <ostream>
using namespace std;
template<unsigned ID,typename Functor>
boost::optional<Functor> &get_local()
{
static boost::optional<Functor> local;
return local;
}
template<unsigned ID,typename Functor>
typename Functor::result_type wrapper()
{
return get_local<ID,Functor>().get()();
}
template<typename ReturnType>
struct Func
{
typedef ReturnType (*type)();
};
template<unsigned ID,typename Functor>
typename Func<typename Functor::result_type>::type get_wrapper(Functor f)
{
(get_local<ID,Functor>()) = f;
return wrapper<ID,Functor>;
}
// ----------------------------------------------------------------------
void test(void (*fptr)())
{
fptr();
}
struct SomeStruct
{
int data;
void some_method()
{
cout << data << endl;
}
void another_method()
{
cout << -data << endl;
}
};
int main()
{
SomeStruct local[] = { {11}, {22}, {33} };
test(get_wrapper<0>( boost::bind(&SomeStruct::some_method,local[0]) ));
test(get_wrapper<1>( boost::bind(&SomeStruct::another_method,local[0]) ));
test(get_wrapper<2>( boost::bind(&SomeStruct::some_method,local[1]) ));
test(get_wrapper<3>( boost::bind(&SomeStruct::another_method,local[1]) ));
test(get_wrapper<4>( boost::bind(&SomeStruct::some_method,local[2]) ));
test(get_wrapper<5>( boost::bind(&SomeStruct::another_method,local[2]) ));
}
P.S. Beaware многопоточного доступа - в таких случаях вы должны использовать данные Thread-local storage.
Ответ 2
Он не компилируется, потому что сторонняя функция ожидает указателя на функцию, но вы пытаетесь передать ей функцию-указатель-член. Эти два типа принципиально разные и не могут быть взаимозаменяемы. На самом деле, указатели на членские функции очень часто странные животные.
Здесь SSCCE, иллюстрирующий проблему, с которой вы сталкиваетесь:
#include <iostream>
#include <iomanip>
using namespace std;
typedef void(*SpeakFn)(void);
void Bark()
{
cout << "WOOF" << endl;
}
void Meow()
{
cout << "meeow" << endl;
}
void SpeakUsing(SpeakFn fn)
{
fn();
}
class Alligator
{
public:
void Speak()
{
cout << "YAWWW" << endl;
}
typedef void(Alligator::*AlligatorSpeakFn)(void);
void SpeakUsing(AlligatorSpeakFn fn)
{
(this->*fn)();
}
};
int main()
{
SpeakUsing(&Bark); // OK
Alligator a;
Alligator::AlligatorSpeakFn mem_fn = &Alligator::Speak;
a.SpeakUsing(mem_fn); // OK
SpeakUsing(mem_fn); // NOT OK -- can't cvt from fn-ptr to mem-fn-ptr
}
Вы не можете вызвать SpeakUsing
с помощью указателя на функцию-член, поскольку он не конвертируется в указатель на функцию.
Вместо этого используйте статическую функцию-член, например:
class Alligator
{
public:
static void Speak()
{
cout << "YAWWW" << endl;
}
typedef void(*AlligatorSpeakFn)(void);
void SpeakUsing(AlligatorSpeakFn fn)
{
fn();
}
};
Ответ 3
Как отмечали другие люди, у вас нет выбора, кроме как использовать глобальные или статические данные для обеспечения контекста вызова вызова как сырой функции. Но предоставленное решение не является общим, оно застряло с пустым списком параметров функтора. Вам потребуется написать вручную wrapper
, get_wrapper
и Func
для каждой различной сигнатуры функции, которую вы хотите связать, и дать им разные имена.
Я хотел бы предложить более обобщенное решение для raw bind:
#include <iostream>
#include <memory>
#include <functional>
#include <cassert>
// Raw Bind - simulating auto storage behavior for static storage data
template <typename BindFunctor, typename FuncWrapper> class scoped_raw_bind
{
public:
typedef scoped_raw_bind<BindFunctor, FuncWrapper> this_type;
// Make it Move-Constructible only
scoped_raw_bind(const this_type&) = delete;
this_type& operator=(const this_type&) = delete;
this_type& operator=(this_type&& rhs) = delete;
scoped_raw_bind(this_type&& rhs): m_owning(rhs.m_owning)
{
rhs.m_owning = false;
}
scoped_raw_bind(BindFunctor b): m_owning(false)
{
// Precondition - check that we don't override static data for another raw bind instance
if(get_bind_ptr() != nullptr)
{
assert(false);
return;
}
// Smart pointer is required because bind expression is copy-constructible but not copy-assignable
get_bind_ptr().reset(new BindFunctor(b));
m_owning = true;
}
~scoped_raw_bind()
{
if(m_owning)
{
assert(get_bind_ptr() != nullptr);
get_bind_ptr().reset();
}
}
decltype(&FuncWrapper::call) get_raw_ptr()
{
return &FuncWrapper::call;
}
static BindFunctor& get_bind()
{
return *get_bind_ptr();
}
private:
bool m_owning;
static std::unique_ptr<BindFunctor>& get_bind_ptr()
{
static std::unique_ptr<BindFunctor> s_funcPtr;
return s_funcPtr;
}
};
// Handy macro for creating raw bind object
// W is target function wrapper, B is source bind expression
#define RAW_BIND(W,B) std::move(scoped_raw_bind<decltype(B), W<decltype(B), __COUNTER__>>(B));
// Usage
///////////////////////////////////////////////////////////////////////////
// Target raw function signature
typedef void (*TargetFuncPtr)(double, int, const char*);
// Function that need to be called via bind
void f(double d, int i, const char* s1, const char* s2)
{
std::cout << "f(" << d << ", " << i << ", " << s1 << ", " << s2 << ")" << std::endl;
}
// Wrapper for bound function
// id is required to generate unique type with static data for
// each raw bind instantiation.
// THE ONLY THING THAT YOU NEED TO WRITE MANUALLY!
template <typename BindFunc, int id = 0> struct fWrapper
{
static void call(double d, int i, const char* s)
{
scoped_raw_bind<BindFunc, fWrapper<BindFunc, id>>::get_bind()(d, i, s);
}
};
int main()
{
using namespace std::placeholders;
auto rf1 = RAW_BIND(fWrapper, std::bind(&f, _1, _2, _3, "This is f trail - 1"));
TargetFuncPtr f1 = rf1.get_raw_ptr();
f1(1.2345, 42, "f1: Bind! Bind!");
auto rf2 = RAW_BIND(fWrapper, std::bind(&f, _1, _2, _3, "This is f trail - 2"));
TargetFuncPtr f2 = rf2.get_raw_ptr();
f2(10.2345, 420, "f2: Bind! Bind!");
auto rf3 = RAW_BIND(fWrapper, std::bind(&f, _1, _2, _3, "This is f trail - 3"));
TargetFuncPtr f3 = rf3.get_raw_ptr();
f3(100.2345, 4200, "f3: Bind! Bind!");
}
Он был протестирован - см. Живое действие здесь
Ответ 4
Нет, не легко. Проблема в том, что указатель функции имеет один кусок информации - адрес функции. Метод нуждается как в этом, так и в адресе объекта, или, альтернативно, ему может быть передан адрес объектов в качестве параметра.
Есть чрезвычайно хакерские способы сделать это, но они будут специфичными для платформы. И очень хаки. Настолько, что я буду рекомендовать глобальные переменные вместо их использования.
Вы знаете, как это сделать, если есть один глобальный экземпляр класса, правильно?