I have a class Door
that implements a method LockCheck()
, and a class Stove
with a method BurnerCheck()
. I want a class House
that takes as a constructor argument either Door::LockCheck
or Stove::BurnerCheck
along with an unknown set of args for the given function. House
would then store the function and its args such that it can call them at some later time. For example,
auto stove = Stove();
auto stove_check = stove.BurnerCheck;
auto burner_args = std::make_tuple<bool, bool>(true, false);
auto house = House(burner_args, stove_check);
// do some other stuff...
house.check_safety(); // internally calls stove.BurnerCheck(burner_args)
What should class House
"looks" like?
So far I have,
template <typename ReturnType, typename... Args>
class House {
public:
House(Args... args, std::function<ReturnType(Args...)> func)
: input_args_(std::forward_as_tuple(args...)),
safety_func_(func) {}
};
private:
Args... input_args_; // Is this the correct declaration?
std::function<ReturnType(Args...)> safety_func_;
};
Notes:
Some preliminary considerations.
1) If you write a template class House
template <typename ReturnType, typename... Args>
class House {
// ...
House(Args... args, std::function<ReturnType(Args...)> func)
: input_args_(std::forward_as_tuple(args...)),
safety_func_(func) {}
where the argument of the check methods are "unknown" (and, I suppose, different from type to type) you have to know that arguments when you define the House
objects and you have different House
types (one House
type for Door
's checks, one House
type for Stove
's checks, etc.) and (before C++17) you can't declare House
object simply as
auto house = House(burner_args, stove_check);
but you have to explicit the template types; something as
House<void, bool, bool> house{burner_args, stove_check};
Suggestion: in you are not interested in the ReturnType
of the checks methods (and if you can ignore it) make House
a not-template class and make a variadic template constructor for it; something as
class House
{
public:
template <typename ... Args, typename F>
House (Args ... as, F f)
2) If you have a template function/method/constructor with a some fixed arguments and a variadic list of arguments, place the variadic list of arguments in last position, so the compiler can deduce the variadic list of types from arguments and there is no need of explicit it.
So the preceding constructor become something as
template <typename F, typename ... Args>
House (F f, Args ... as)
3) As far I know, there is no way to pass a pointer to an actual method to a function o to a variable; so no
auto stove_check = stove.BurnerCheck;
and no stove_check
as argument of House
constructor.
The usual way that I know for this sort of problem is pass the object (stove
) and a pointer to BurnerCheck
method referred to the class, not to the object; something as
auto house { House(stove, &Stove::BurnerCheck, /* variadic args */) };
Now the contructor become
template <typename T, typename M, typename ... Args>
House (T t, M m, Args ... as)
and you can call the BurnerCheck()
method of stove
as
(t.*m)(as...)
Now my suggested House
class: a class with a std::function<void(void)>
member that is initialized, in House
constructor, with a lambda that capture object, pointer method and arguments.
And a check_safety()
method that simply call that member.
Something as follows
class House
{
private:
std::function<void(void)> fn;
public:
template <typename T, typename M, typename ... Args>
House (T t, M m, Args ... as) : fn{[=]{ (t.*m)(as...); }}
{ }
void check_safety ()
{ fn(); }
};
The following is a full working example
#include <iostream>
#include <functional>
struct Door
{ void LockCheck (int, long) const { std::cout << "Door" << std::endl; } };
struct Stove
{ void BurnerCheck (char) const { std::cout << "Stove" << std::endl; } };
class House
{
private:
std::function<void(void)> fn;
public:
template <typename T, typename M, typename ... Args>
House (T t, M m, Args ... as) : fn{[=]{ (t.*m)(as...); }}
{ }
void check_safety ()
{ fn(); }
};
int main ()
{
auto stove { Stove{} };
auto door { Door{} };
auto house1 { House{stove, &Stove::BurnerCheck, 'a'} };
auto house2 { House{door, &Door::LockCheck, 1, 2L} };
std::cout << "Some other stuff" << std::endl;
house1.check_safety();
house2.check_safety();
}
If you're interested in the value returned from the checked method... I suppose you can make House
a template class with only the ReturnType
parameter and adjust the class consequently.
You can store all the arguments as a tuple. You can then call safety_func_
by unpacking the tuple into function arguments. The unpacking can directly be done in C++17 using std::apply.
template <typename ReturnType, typename... Args>
class House {
public:
House(Args... args, std::function<ReturnType(Args...)> func)
: input_args_(std::forward<Args>(args)...),
safety_func_(func) {}
// Require C++17
ReturnType call_safety() {
return std::apply(safety_func_, input_args_);
}
private:
std::tuple<Args...> input_args_;
std::function<ReturnType(Args...)> safety_func_;
};
For a pure C++11 tuple unpacking solution, see this post