I have a number of callback functions with different signatures. Ideally, I would like to put these in a vector and call the appropriate one depending on certain conditions.
e.g.
void func1(const std::string& value);
void func2(const std::string& value, int min, int max);
const std::vector<std::function<void(std::string)>> functions
{
func1,
func2,
};
I realise the above isn't possible, but I wonder if there are any alternatives I should consider. I haven't been able to find any yet, and I've experimented with std::bind
but not managed to achieve what I want.
Is such a thing possible?
C++ is a statically-typed language. A vector will hold an object of a single type, and only a single type.
std::string offers a very different and much expanded interface compared to std::vector<> . While the latter is just a boring old sequence of elements, the former is actually designed to represent a string and therefore offers an assortment of string-related convenience functions.
A std::vector can never be faster than an array, as it has (a pointer to the first element of) an array as one of its data members. But the difference in run-time speed is slim and absent in any non-trivial program. One reason for this myth to persist, are examples that compare raw arrays with mis-used std::vectors.
You "can" use a vector over a queue, if the queue lifetime is short or if you know the maximum size of your queue. Just use a vector, push_back in it, and keep an index of where your "head" is.
You haven't said what you expect to be able to do with func2
after putting it in a vector with the wrong type.
You can easily use std::bind
to put it in the vector if you know the arguments ahead of time:
const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, 5, 6)
};
Now functions[1]("foo")
will call func2("foo", 5, 6)
, and will pass 5
and 6
to func2
every time.
Here's the same thing using a lambda instead of std::bind
const std::vector<std::function<void(std::string)>> functions
{
func1,
[=](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};
If you don't know the arguments yet, you can bind references to some variables:
int func2_arg1 = 5;
int func2_arg2 = 6;
const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, std::ref(func2_arg1), std::ref(func2_arg2))
};
Now functions[1]("foo")
will call func2("foo", func2_arg1, func2_arg2)
, and you can assign new values to the integers to pass different arguments to func2
.
And using a lambda function instead of std::bind
const std::vector<std::function<void(std::string)>> functions
{
func1,
[&](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};
This is pretty ugly though, as you need to keep the int
variables around for as long as the callable object (the closure or the bind expression) referring to them exists.
What you want is possible through polymorphism
. The idea is to create a class with a specific signature, which at runtime will call different methods. For example:
#include <iostream>
#include <functional>
#include <memory>
#include <vector>
void foo(int) {
std::cout << "I'm foo!\n";
}
int bar(char, double) {
std::cout << "I'm bar!\n";
}
class MyFunction {
public:
virtual ~MyFunction(){}
virtual void operator()() = 0;
};
class MyFunctionA : public MyFunction {
public:
virtual void operator()() {
foo(4);
}
};
class MyFunctionB : public MyFunction {
public:
MyFunctionB(std::function<int(char,double)> f, char arg1, double arg2) : fun_(f), arg1_(arg1), arg2_(arg2) {}
virtual void operator()() {
fun_(arg1_, arg2_);
}
private:
std::function<int(char,double)> fun_;
char arg1_;
double arg2_;
};
int main() {
using MyFunPtr = std::unique_ptr<MyFunction>;
std::vector<MyFunPtr> v;
v.emplace_back(new MyFunctionA());
v.emplace_back(new MyFunctionB(bar, 'c', 3.4));
for ( auto&& myfun : v ) {
(*myfun)();
}
return 0;
}
You can make the derived classes as complicated as you need be, but since in the end they all have the same interface you will be able to call all of them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With