How to Store Functional Objects with Different Signatures in a Container

How to store functional objects with different signatures in a container?

#include <functional>
#include <iostream>
#include <string>
#include <map>

class api {
// maps containing the different function pointers
typedef void(*voidfuncptr)();
typedef int(*stringcrintptr)(std::string, const int&);

std::map<std::string, voidfuncptr> voida;
std::map<std::string, stringcrintptr> stringcrint;
public:
// api temp class
// given an api and a name, it converts to a function pointer
// depending on parameters used
class apitemp {
const std::string n;
const api* p;
public:
apitemp(const std::string& name, const api* parent)
: n(name), p(parent) {}
operator voidfuncptr()
{ return p->voida.find(n)->second; }
operator stringcrintptr()
{ return p->stringcrint.find(n)->second; }
};

// insertion of new functions into appropriate maps
void insert(const std::string& name, voidfuncptr ptr)
{ voida[name]=ptr; }
void insert(const std::string& name, stringcrintptr ptr)
{ stringcrint[name]=ptr; }
// operator[] for the name gets halfway to the right function
apitemp operator[](std::string n) const
{ return apitemp(n, this); }
};

Usage:

api myMap; 

int hello_world(std::string name, const int & number )
{
name += "!";
std::cout << "Hello, " << name << std::endl;
return number;
}

int main()
{
myMap.insert("my_method_hello", &hello_world );
int a = myMap["my_method_hello"]("Tim", 25);
}

Not very pretty. Better advice is to not do anything even remotely like whatever it is you're trying to do.

Note that this requires all functions with the same parameters to return the same type.

How to store functional objects with different signatures in modern C++

You can write your own any. Without all the compiler workarounds and stuff, boost::any can be written in about 30 lines of code.

Containers for different signature functions

How about something like this:

template <typename result_t, typename... args_t>
class Memoizer
{
public:
typedef result_t (*function_t)(args_t...);
Memoizer(function_t func) : m_func(func) {}

result_t operator() (args_t... args)
{
auto args_tuple = make_tuple(args...);

auto it = m_results.find(args_tuple);
if (it != m_results.end())
return it->second;

result_t result = m_func(args...);
m_results.insert(make_pair(args_tuple, result));
return result;
}

protected:
function_t m_func;
map<tuple<args_t...>, result_t> m_results;
};

Usage is like this:

// could create make_memoizer like make_tuple to eliminate the template arguments
Memoizer<double, double> memo(fabs);
cout << memo(-123.456);
cout << memo(-123.456); // not recomputed

Is there a way to store different function pointer types in a container

To me, this seems rather contrived. You need the right function in the right place with the right arguments, for the right name. If you declare a function pointer typedef, and use a single line reinterpret_cast to assign the function, you need a good 40+ functions before it's up to the code you have provided here - and that's simple step and repeat type code, so it's easy to follow and easy to maintain. No templates, no variable arguemnts, etc.

Obviously, you'd still have to produce a function that returns a void * from a name. But that would be just what you have now minus the reinterpret_cast.

Store functions with different signatures in a map

You can type-erase the function types into a container, then provide a template operator(). This will throw std::bad_any_cast if you get it wrong.

N.B. because of the type erasure, you will have to specify exactly matching arguments at the call site, as e.g. std::function<void(std::string)> is distinct from std::function<void(const char *)>, even though both can be called with a value like "Hello".

#include <any>
#include <functional>
#include <map>
#include <string>
#include <iostream>

template<typename Ret>
struct AnyCallable
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(std::forward<F>(fun))) {}
template<typename ... Args>
AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
Ret operator()(Args&& ... args)
{
return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...);
}
std::any m_any;
};

void foo(int x, int y)
{
std::cout << "foo" << x << y << std::endl;
}

void bar(std::string x, int y, int z)
{
std::cout << "bar" << x << y << z << std::endl;
}

using namespace std::literals;

int main()
{
std::map<std::string, AnyCallable<void>> map;

map["foo"] = &foo; //store the methods in the map
map["bar"] = &bar;

map["foo"](1, 2); //call them with parameters I get at runtime
map["bar"]("Hello, std::string literal"s, 1, 2);
try {
map["bar"]("Hello, const char *literal", 1, 2); // bad_any_cast
} catch (std::bad_any_cast&) {
std::cout << "mismatched argument types" << std::endl;
}
map["bar"].operator()<std::string, int, int>("Hello, const char *literal", 1, 2); // explicit template parameters

return 0;
}

Storing boost::function objects in a container

Approach #1:

http://www.boost.org/doc/libs/1_51_0/doc/html/function/tutorial.html#id1546064

Function object wrappers can be compared via == or != against any function object that can be stored within the wrapper.

So, one of solutions, is to define special type for UnregisterCallback's parameter (which also supports type erasure). That is based on fact, that you can compare boost::function with functor/function - as the result you still will have vector of boost::function, new type is required only for places where you need to perform comparison, e.g. UnregisterCallback:

LIVE DEMO

#include <boost/scoped_ptr.hpp>
#include <boost/function.hpp>
#include <algorithm>
#include <iostream>
#include <typeinfo>
#include <ostream>
#include <vector>
#include <string>

using namespace std;
using namespace boost;

typedef int KeyEvent;
typedef function<void (const KeyEvent &)> KeyCallback;

struct AbstractCallback
{
virtual bool equals(const KeyCallback &f) const=0;
virtual ~AbstractCallback(){}
};

template<typename Callback>
struct ConcreteCallback : AbstractCallback
{
const Callback &callback;
explicit ConcreteCallback(const Callback &p_callback) : callback(p_callback) {}
virtual bool equals(const KeyCallback &f) const
{
return callback == f;
}
};

struct KeyCallbackChecker
{
scoped_ptr<AbstractCallback> func;
public:
template<typename Func>
KeyCallbackChecker(const Func &f) : func(new ConcreteCallback<Func>(f)) {}
friend bool operator==(const KeyCallback &lhs,const KeyCallbackChecker &rhs)
{
return rhs.func->equals(lhs);
}
friend bool operator==(const KeyCallbackChecker &lhs,const KeyCallback &rhs)
{
return rhs==lhs;
}
};

void func1(const KeyEvent &)
{
cout << "func1" << endl;
}

void func3(const KeyEvent &)
{
cout << "func3" << endl;
}

class func2
{
int data;
public:
explicit func2(int n) : data(n) {}
friend bool operator==(const func2 &lhs,const func2 &rhs)
{
return lhs.data==rhs.data;
}
void operator()(const KeyEvent &)
{
cout << "func2, data=" << data << endl;
}
};

struct Caller
{
template<typename F> void operator()(F f)
{
f(1);
}
};

class Callbacks
{
vector<KeyCallback> v;
public:
void register_callback(const KeyCallback &callback)
{
v.push_back(callback);
}
void unregister_callback(const KeyCallbackChecker &callback)
{
vector<KeyCallback>::iterator it=find(v.begin(),v.end(),callback);
if(it!=v.end())
v.erase(it);
}
void call_all()
{
for_each(v.begin(),v.end(),Caller());
cout << string(16,'_') << endl;
}
};

int main(int argc,char *argv[])
{
Callbacks cb;
cb.register_callback(func1);
cb.register_callback(func2(1));
cb.register_callback(func2(2));
cb.register_callback(func3);
cb.call_all();

cb.unregister_callback(func2(2));
cb.call_all();
cb.unregister_callback(func1);
cb.call_all();

return 0;
}

Output is:

func1
func2, data=1
func2, data=2
func3
________________
func1
func2, data=1
func3
________________
func2, data=1
func3
________________

Pros:

  • We still use boost::function for registring and storing in vector
  • Functor object should have defined comparison only when there is need to pass it to unregister_callback
  • It can be easly generalized - just add one template parameter instead of using typedefed KeyCallback. So, can be easily used at other places, for other types of callbacks.

Cons:

  • If user already has callback wrapped to boost::function - it can't be used with unregister_callback, because it requires something that can be compared to boost::function (e.g. function pointer, or functor with defined comparison)


Approach #2:

Another approach is to implement custom boost::function-like solution, which accepts comparable-only callbacks.

LIVE DEMO

#include <boost/shared_ptr.hpp>
#include <algorithm>
#include <iostream>
#include <typeinfo>
#include <ostream>
#include <vector>

using namespace std;
using namespace boost;

typedef int KeyEvent;
typedef void (*func_type)(const KeyEvent &);

struct AbstractCallback
{
virtual void operator()(const KeyEvent &p)=0;
virtual bool compare_to(const std::type_info &rhs_type,const void *rhs) const=0;
virtual bool compare_to(const std::type_info &rhs_type,const func_type *rhs) const=0;
virtual bool equals(const AbstractCallback &rhs) const=0;
};

template<typename Callback>
struct ConcreteCallback : AbstractCallback
{
Callback callback;
ConcreteCallback(Callback p_callback) : callback(p_callback) {}
void operator()(const KeyEvent &p)
{
callback(p);
}
bool compare_to(const std::type_info &rhs_type,const void *rhs) const
{
return (typeid(Callback)==rhs_type) &&
( *static_cast<const Callback*>(rhs) == callback );
}
bool compare_to(const std::type_info &rhs_type,const func_type *rhs) const
{
return false;
}
bool equals(const AbstractCallback &rhs) const
{
return rhs.compare_to(typeid(Callback),&callback);
}
};

template<>
struct ConcreteCallback<func_type> : AbstractCallback
{
func_type callback;
ConcreteCallback(func_type p_callback) : callback(p_callback) {}
void operator()(const KeyEvent &p)
{
callback(p);
}
bool compare_to(const std::type_info &rhs_type,const void *rhs) const
{
return false;
}
bool compare_to(const std::type_info &rhs_type,const func_type *rhs) const
{
return *rhs == callback;
}
bool equals(const AbstractCallback &rhs) const
{
return rhs.compare_to(typeid(func_type),&callback);
}
};

struct KeyCallback
{
shared_ptr<AbstractCallback> func;
public:
template<typename Func>
KeyCallback(Func f) : func(new ConcreteCallback<Func>(f)) {}
friend bool operator==(const KeyCallback &lhs,const KeyCallback &rhs)
{
return lhs.func->equals(*rhs.func);
}
void operator()(const KeyEvent &p)
{
(*func)(p);
}
};

void func1(const KeyEvent &)
{
cout << "func1" << endl;
}

void func3(const KeyEvent &)
{
cout << "func3" << endl;
}

class func2
{
int data;
public:
func2(int n) : data(n) {}
friend bool operator==(const func2 &lhs,const func2 &rhs)
{
return lhs.data==rhs.data;
}
void operator()(const KeyEvent &)
{
cout << "func2, data=" << data << endl;
}
};

struct Caller
{
template<typename F>
void operator()(F f)
{
f(1);
}
};

int main(int argc,char *argv[])
{
vector<KeyCallback> v;

v.push_back(KeyCallback(func1));
v.push_back(KeyCallback(func1));
v.push_back(KeyCallback(func1));

v.push_back(KeyCallback(func2(1)));
v.push_back(KeyCallback(func2(1)));

v.push_back(KeyCallback(func2(2)));
v.push_back(KeyCallback(func2(2)));
v.push_back(KeyCallback(func2(2)));
v.push_back(KeyCallback(func2(2)));

v.push_back(KeyCallback(func3));

for_each(v.begin(),v.end(),Caller());

cout << count(v.begin(),v.end(),KeyCallback(func1)) << endl;
cout << count(v.begin(),v.end(),KeyCallback(func2(1))) << endl;
cout << count(v.begin(),v.end(),KeyCallback(func2(2))) << endl;
cout << count(v.begin(),v.end(),KeyCallback(func3)) << endl;
return 0;
}

Output is:

func1
func1
func1
func2, data=1
func2, data=1
func2, data=2
func2, data=2
func2, data=2
func2, data=2
func3
3
2
4
1

Pros:

  • We use same type in register/unregister callback. User may store his functions and functors outside wrapped to KeyCallback - and pass KeyCallback to our unregister_callback.
  • There is no dependency on boost::function

Cons:

  • Functor object must have defined comparison even if it is not used with unregister_callback
  • If user already has callback wrapped to boost::function - it can't be converted to our KeyCallback, becuase it requires defined comparison.
  • If you need similar functionality at other places, with different kind of callbacks - then our boost::function-like classes should be improved (taking different and several parameters, etc, etc) or we may extract and modify boost::funciton itself.


Approach #3:

Here we are creating new class which is inherited from std/boost ::function

LIVE DEMO

#include <type_traits>
#include <functional>
#include <algorithm>
#include <stdexcept>
#include <iostream>
#include <typeinfo>
#include <utility>
#include <ostream>
#include <vector>
#include <string>

using namespace std;

// _____________________________Implementation__________________________________________

#define USE_VARIADIC_TEMPLATES 0

template<typename Callback,typename Function>
bool func_compare(const Function &lhs,const Function &rhs)
{
typedef typename conditional<is_function<Callback>::value,typename add_pointer<Callback>::type,Callback>::type request_type;
if (const request_type* lhs_internal = lhs.template target<request_type>())
if (const request_type* rhs_internal = rhs.template target<request_type>())
return *rhs_internal == *lhs_internal;
return false;
}

#if USE_VARIADIC_TEMPLATES
#define FUNC_SIG_TYPES typename ...Args
#define FUNC_SIG_TYPES_PASS Args...
#else
#define FUNC_SIG_TYPES typename function_signature
#define FUNC_SIG_TYPES_PASS function_signature
#endif

template<FUNC_SIG_TYPES>
struct function_comparable: function<FUNC_SIG_TYPES_PASS>
{
typedef function<FUNC_SIG_TYPES_PASS> Function;
bool (*type_holder)(const Function &,const Function &);
public:
function_comparable(){}
template<typename Func>
function_comparable(Func f_)
: Function(f_), type_holder(func_compare<Func,Function>)
{
}
template<typename Func>
function_comparable &operator=(Func f_)
{
Function::operator=(f_);
type_holder=func_compare<Func,Function>;
return *this;
}
friend bool operator==(const Function &lhs,const function_comparable &rhs)
{
return rhs.type_holder(lhs,rhs);
}
friend bool operator==(const function_comparable &lhs,const Function &rhs)
{
return rhs==lhs;
}
friend void swap(function_comparable &lhs,function_comparable &rhs)// noexcept
{
lhs.swap(rhs);
lhs.type_holder.swap(rhs.type_holder);
}
};

// ________________________________Example______________________________________________

typedef void (function_signature)();

void func1()
{
cout << "func1" << endl;
}

void func3()
{
cout << "func3" << endl;
}

class func2
{
int data;
public:
explicit func2(int n) : data(n) {}
friend bool operator==(const func2 &lhs,const func2 &rhs)
{
return lhs.data==rhs.data;
}
void operator()()
{
cout << "func2, data=" << data << endl;
}
};
struct Caller
{
template<typename Func>
void operator()(Func f)
{
f();
}
};
class Callbacks
{
vector<function<function_signature>> v;
public:
void register_callback_comparator(function_comparable<function_signature> callback)
{
v.push_back(callback);
}
void register_callback(function<function_signature> callback)
{
v.push_back(callback);
}
void unregister_callback(function_comparable<function_signature> callback)
{
auto it=find(v.begin(),v.end(),callback);
if(it!=v.end())
v.erase(it);
else
throw runtime_error("not found");
}
void call_all()
{
for_each(v.begin(),v.end(),Caller());
cout << string(16,'_') << endl;
}
};

int main()
{
Callbacks cb;
function_comparable<function_signature> f;
f=func1;
cb.register_callback_comparator(f);

cb.register_callback(func2(1));
cb.register_callback(func2(2));
cb.register_callback(func3);
cb.call_all();

cb.unregister_callback(func2(2));
cb.call_all();
cb.unregister_callback(func1);
cb.call_all();
}

Output is:

func1
func2, data=1
func2, data=2
func3
________________
func1
func2, data=1
func3
________________
func2, data=1
func3
________________

Pros:

  • We can use same type in register/unregister callback. User may store his functions and functors outside wrapped to KeyCallback - and pass KeyCallback to our unregister_callback. Morever in this version we may use plain boost::function for parameter of register function.
  • We still can use boost::function for registring and storing in vector
  • When we use boost::function for registering, functor object should have defined comparison only when there is need to pass it to unregister_callback.
  • It is generalized - so, can be easily used at other places, for other types of callbacks.
  • This version is based on plain function pointer instead of allocation+abstract class (vptr). So it has one less inderection, it easier to manage.

Cons:

  • If user already has callback wrapped to boost::function - it can't be used with unregister_callback, because it requires something that can be compared to boost::function (e.g. function pointer, or functor with defined comparison)


EDIT:

Awesome, I'm trying it out #1 right now, but I do not quite understand why it works when we apply our own == operators?

boost::function can be compared against functions or functors, but not against another boost::function:

#include <boost/function.hpp>

void f1(){}
void f2(){}

int main()
{
boost::function<void ()> bf1(f1),bf2(f2);
bf1 == f1; // Works OK
//bf1 == bf2; - COMPILE ERROR
return 0;
}

In our #1 approach, we do comparison similar to "bf1 == f1;". KeyCallbackChecker captures functor/function and performs such kind of comparison inside ConcreteCallback::equals.



Related Topics



Leave a reply



Submit