So imagine we had 2 functions (void : ( void ) )
and (std::string : (int, std::string))
and we could have 10 more. All (or some of them) take in di
Complete example here and here.
We can use type erasure like boost::any does. But since we want to hold only functions, we can come up with more convenient usage.
The first thing we need is function signature deduction. See here.
template
struct function_traits : public function_traits
{};
template
struct function_traits>
{
typedef R result_type;
typedef typename std::function type;
typedef typename std::function typeNoRet;
};
template
struct function_traits
{
typedef R result_type;
typedef typename std::function type;
typedef typename std::function typeNoRet;
};
template
struct function_traits
{
typedef R result_type;
typedef typename std::function type;
typedef typename std::function typeNoRet;
};
To implement type erasure we need base class which is not template, and derived template class. FunctionHolder will store base class pointer. Constructor will use function_traits to determine correct derived class type.
class FunctionHolder {
private:
struct BaseHolder {
BaseHolder() {}
virtual ~BaseHolder() {}
};
template
struct Holder : public BaseHolder {
Holder(T arg) : mFptr(arg) {}
template
void Call(Args&&...args) {
mFptr(std::forward(args)...);
}
template
R CallRet(Args&&...args) {
return mFptr(std::forward(args)...);
}
T mFptr;
};
public:
template
FunctionHolder(T t) : mBaseHolder(new Holder::type>(t))
, mBaseHolderNoRet(new Holder::typeNoRet>(t)) {}
template
FunctionHolder(T&& t, Args&&... args) : mBaseHolder(new Holder::type>
(std::bind(std::forward(t), std::forward(args)...)))
, mBaseHolderNoRet(new Holder::typeNoRet>
(std::bind(std::forward(t), std::forward(args)...))) {}
void operator()() {
this->operator()<>();
}
template
void operator()(Args&&... args) {
auto f = dynamic_cast >*>(mBaseHolderNoRet.get());
if (f) {
f->Call(std::forward(args)...);
return;
}
throw std::invalid_argument("");
}
template
R call(Args&&... args) {
auto f = dynamic_cast>*>(mBaseHolder.get());
if (f) {
return f->template CallRet(std::forward(args)...);
}
throw std::invalid_argument("");
}
private:
std::unique_ptr mBaseHolder;
std::unique_ptr mBaseHolderNoRet;
};
In this case FunctionHolder stores 2 pointers of the BaseHolder, the first one with the correct signature and second one returning void. To avoid overheads you may remove one of them, if your are going to always specify return value or never use it.
And finally we may use it like this.
struct st0 {
st0(int x) : mX(x) {}
std::string print(int p) {
std::cout << "st0::print "
<< mX << " " << p << std::endl;
return "ret_from_st0::print";
}
int mX;
};
struct st1 {
st1(int x) : mX(x) {}
void operator()() {
std::cout << "st1::operator() "
<< mX << " " << std::endl;
}
int mX;
};
void Func0(int a, int b) {
std::cout << "Func0. "
<< " a: " << a
<< " b: " << b << std::endl;
}
void Func1(int a, int b, std::string str) {
std::cout << "Func0. "
<< " a: " << a
<< " b: " << b
<< " str: " << str << std::endl;
}
uint64_t Func2(int a, int b, std::string str) {
std::cout << "Func0. "
<< " a: " << a
<< " b: " << b
<< " str: " << str << std::endl;
return 0xBAB0CAFE;
}
int main() {
try {
// void(int, int)
FunctionHolder ex1(&Func0);
ex1(1,12);
// void(int, int, std::string)
FunctionHolder ex2(&Func1);
ex2(1, 12, std::string("Some text here"));
// int(int, int, std::string)
// call and print return value
FunctionHolder ex3(&Func2);
std::cout << "Ret: " << std::hex << ex3.call(123, 3211, std::string("another text"))
<< std::dec << std::endl;
// call and drop return value
ex3(123, 3211, std::string("another text"));
// Hold std::function
std::function ex4 = std::bind(&Func0, 1, std::placeholders::_1);
FunctionHolder c(std::function(std::bind(&Func0, 1, std::placeholders::_1)));
ex4(12);
// will bind to st0 member function print
st0 st0object(8955);
FunctionHolder ex5(&st0::print, st0object, std::placeholders::_1);
ex5(2222);
// call and print return value
std::cout << "Ret: " << ex5.call(7531) << std::endl;
// wrap lambda function with std::function and pass to holder
FunctionHolder ex6(std::function([]() {std::cout << "lambda function called" << std::endl;}));
ex6();
// functor object st1
FunctionHolder ex7(st1(123654));
ex7();
// Will throw, because st1::operator() gets no arguments
ex7(123);
} catch (std::invalid_argument &e) {
std::cout << "Invalid argument(s) were passed" << std::endl;
// error handling here...
}
return 0;
}
FunctionHolder may store C like function pointers, functor objects, lambda functions and member function pointers. There are only 2 exceptions to remember.
FunctionHolder fh(std::function(std::bind(&Func1, 888, 333, std::placeholders::_1)));
fh(std::string("Ok."));
In case of passing wrong arguments, FunctionHolder will throw std::invalid_argument.