简体   繁体   中英

Is it possible for C++ to implement function pointers point to different parameter lists?

I recently wrote about the function of class member function callbacks. I need to save the callback object and function pointer, then call the function pointer and fill in the appropriate parameters where the callback is needed.

I started out as a form of typedef void (AAA::*Function)(int a, int b); , but when I need to support different parameter lists of member function, I obviously need a dynamic way to implement it.

class AAA
{
public:
    int add(int a, int b)
    {
        return (a + b);
    }
};

class BBB
{
public:

    void setValue(std::string value)
    {
        this->value = value;
    }

private:
    std::string value;
};

class CCC
{
public:

    void bind(??? p)    // Binding objects and callback functions.
    {
        this->p = p;
    }

    template <class... Args>
    auto callback(Args&&... args)   // Autofill parameter list.
    {
        return this->p(std::forward<Args>(args)...);
    }

private:
    ??? p;  // How is this function pointer implemented?
};

int main()
{
    AAA aaa;
    BBB bbb;

    CCC ccc;
    ccc.bind(???(aaa, &AAA::add));
    int number = ccc.callback(5, 6);

    ccc.bind(???(bbb, &BBB::setValue));
    ccc.callback("Hello");

    system("pause");
    return 0;
}

I don't know how can I implement the function pointer "???".

C++ is a type-safe language. This means that you cannot do exactly what you've outlined in your question. A pointer to a function that takes specific parameters is a different type from a pointer to a function that takes different parameters. This is fundamental to C++.

std::bind can be use to type-erase different types to the same type, but you get a single type at the end, that can be called only with a matching set of parameters (if any). It is not possible to invoke the "underlying" bound function, with its real parameters. That's because the whole purpose of std::bind is to make them disappear, and inaccessible. That's what std::bind is for.

You only have a limited set options to make this work while staying with the bounds and constraints of C++'s type-safety.

  1. Make use of a void * , in some fashion. Actually, don't. Don't do that. That will just cause more problems, and headache.

  2. Have a separate list and classes of callbacks, one list for each set of callbacks that take a specific set of parameters. You must know, at the point of invoking a callback, what parameters you intend to pass. So, just get your callback from the appropriate list.

  3. Make use of std::variant . The type-safe std::variant is C++17 only (but boost has a similar template that's mostly equivalent, and available with older C++ revisions). All your callbacks take a single std::variant parameter, a variant of every possible set of parameters (designated as a std::tuple of them, or some class/struct instance). Each callback will have to decide what to do if it receives a std::variant containing the wrong parameter value.

Alternatively, the std::variant can be a variant of different std::function types, thus shifting the responsibility of type-checking to the caller, instead of each callback.

The bottom line is that C++ is fundamentally a type-safe language; and this is precisely one of the reasons why one would choose to use C++ instead of a different language that does not have the same kind of type-safety.

But being a type-safe language, that means that you have certain limitations when it comes to juggling different types together. Specifically: you can't. Everything in C++ is always, and must be, a single type.

You basically are asking to have fully dynamicly typed and checked function calls.

To have fully dynamic function calls, you basically have to throw out the C++ function call system.

This is a bad idea, but I'll tell you how to do it.

A dynamicly callable object looks roughly like this:

using dynamic_function = std::function< std::any( std::vector<std::any> ) >

where use use

struct nothing_t {};

when we want to return void .

Then you write machinery that takes an object and a specific signature, and wraps it up.

template<class R, class...Args, class F>
struct dynamic_function_maker {
  template<std::size_t...Is>
  dynamic_function operator()(std::index_sequence<Is...>, F&& f)const {
    return [f=std::forward<F>(f)](std::vector<std::any> args)->std::any {
      if (sizeof...(Is) != args.size())
        throw std::invalid_argument("Wrong number of arguments");
      if constexpr( std::is_same< std::invoke_result_t<F const&, Args... >, void >{} )
      {
        f( std::any_cast<Args>(args[Is])... );
        return nothing_t{};
      }
      else
      {
        return f( std::any_cast<Args>(args[Is])... );
      }
    };
  }
  dynamic_function operator()(F&& f)const {
    return (*this)(std::make_index_sequence<sizeof...(Args)>{}, std::forward<F>(f));
  }
};
template<class R, class...Args, class F>
dynamic_function make_dynamic_function(F f){
  return dynamic_function_maker<R,Args...,F>{}(std::forward<F>(f));
}

next you'll want to deduce signatures of function pointers and the like:

template<class R, class...Args>
dynamic_function make_dynamic_function(R(*f)(Args...)){
  return dynamic_function_maker<R,Args...,F>{}(std::forward<F>(f));
}
template<class Tclass R, class...Args>
dynamic_function make_dynamic_function(T* t, R(T::*f)(Args...)){
  return dynamic_function_maker<R,Args...,F>{}(
    [t,f](auto&&...args)->decltype(auto){return (t->*f)(decltype(args)(args)...);}
  );
}

then after fixing typos above you should be able to solve your original problem.

Again, as someone who can actually write and understand the above code, I strongly advise you not to use it . It is fragile and dangerous.

There is almost never a good reason to store callbacks in places where you don't know what the arguments you are going to call it with.

There should be a different type and instance of CCC for each set of arguments you want to call it with. 99/100 times when people ask this question, they are asking the wrong question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM