web-dev-qa-db-fra.com

convertir std :: bind en pointeur de fonction

J'ai une bibliothèque tierce qui a une méthode qui prend un pointeur de fonction comme premier paramètre:

int third_party_method(void (*func)(double*, double*, int, int, double*), ...);

Je veux passer un pointeur vers une méthode de classe qui est déclarée comme suit:

class TestClass
{
    public:
        void myFunction (double*, double*, int, int, void*);

J'ai essayé de passer cette fonction comme suit:

TestClass* tc = new TestClass();
using namespace std::placeholders;
third_party_method(std::bind(&TestClass::myFunction, tc, _1, _2, _3, _4, _5), ...);

Cependant, cela ne compile pas:

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>>
]

Existe-t-il un moyen de transmettre le membre à la fonction?

43
Nico Schertler

Existe-t-il un moyen de transmettre le membre à la fonction?

À moins que votre objet de classe ne soit une sorte d'objet global - ce n'est pas possible. Parce que les objets peuvent contenir des données, alors que le pointeur de fonction est simplement un pointeur sur la fonction - il ne contient aucun contexte d'exécution, seulement un au moment de la compilation.

Si vous acceptez d'avoir des ID uniques au moment de la compilation pour chaque passage de rappel, vous pouvez utiliser l'approche générale suivante.

Usage:

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

Il peut ne pas nécessiter d'ID unique pour chaque appel, par exemple parce que les foncteurs peuvent déjà avoir différents types ou que la portée d'exécution de leur utilisation ne se chevauche pas. Mais il est plus sûr d'utiliser à chaque fois un ID unique.

La mise en oeuvre:

démo en direct

#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. Attention à l'accès multi-thread - dans de tels cas, vous devez utiliser une sorte de données Thread-local storage .

19
Evgeny Panasyuk

Comme d'autres personnes l'ont mentionné, vous n'avez pas d'autre choix que d'utiliser des données globales ou statiques pour fournir le contexte d'appel de liaison en tant que fonction brute. Mais la solution fournie n'est pas générale, elle est bloquée avec une liste de paramètres vide de fonctor. Vous devrez écrire manuellement wrapper, get_wrapper et Func pour chaque signature de fonction différente que vous souhaitez lier et leur donner des noms différents.

Je voudrais proposer une solution plus généralisée pour le bind brut:

#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!");
}

Il a été testé - voir Live Action Here

5
Rost

Il ne se compile pas car la fonction tierce attend un pointeur vers une fonction, mais vous essayez de lui passer un pointeur vers - membre -une fonction. Les deux types sont fondamentalement différents , et ne peuvent pas être échangés. En fait, les fonctions pointeurs vers membre sont très souvent animaux étranges .

Voici un SSCCE illustrant le problème que vous rencontrez:

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

Vous ne pouvez pas appeler SpeakUsing avec une fonction pointeur-sur-membre car elle n'est pas convertible en pointeur-sur-fonction.

Utilisez plutôt une fonction membre statique, telle que:

class Alligator
{
public:
    static void Speak()
    {
        cout << "YAWWW" << endl;
    }
    typedef void(*AlligatorSpeakFn)(void);

    void SpeakUsing(AlligatorSpeakFn fn)
    {
        fn();
    }
};
5
John Dibling

Non, pas facilement. Le problème est qu'un pointeur de fonction a un bloc d'informations - l'adresse de la fonction. Une méthode a besoin à la fois de cela et de l'adresse de l'objet, ou bien il peut être transmis l'adresse des objets en tant que paramètre.

Il existe des moyens extrêmement complexes de le faire, mais ils seront spécifiques à la plate-forme. Et extrêmement hackey. À tel point que je recommanderai des variables globales au lieu de les utiliser.

Vous savez comment faire cela s'il y a une seule instance globale de la classe, non?