-->

C++ object keeps templated function and args as me

2019-07-15 04:34发布

问题:

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:

  • C++11

  • I've already seen related SO questions such as this and this.

回答1:

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.



回答2:

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