Возможно ли для C ++ указатели функций указывать на разные списки параметров? - PullRequest
0 голосов
/ 14 января 2019

Я недавно писал о функции обратных вызовов функций членов класса. Мне нужно сохранить объект обратного вызова и указатель на функцию, затем вызвать указатель на функцию и заполнить соответствующие параметры там, где требуется обратный вызов.

Я начинал как форма typedef void (AAA::*Function)(int a, int b);, но когда мне нужно поддерживать разные списки параметров функции-члена, мне, очевидно, нужен динамический способ ее реализации.

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;
}

Я не знаю, как я могу реализовать указатель функции "???".

Ответы [ 2 ]

0 голосов
/ 14 января 2019

В основном вы запрашиваете полностью динамически набранные и проверенные вызовы функций.

Чтобы иметь полностью динамические вызовы функций, вам, в основном, нужно выбросить систему вызова функций C ++.

Это плохая идея, но я скажу вам, как это сделать.

Динамически вызываемый объект выглядит примерно так:

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

где использовать

struct nothing_t {};

когда мы хотим вернуть void.

Затем вы пишете механизм, который берет объект и определенную подпись и оборачивает его.

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));
}

затем вы захотите вывести сигнатуры указателей функций и тому подобное:

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)...);}
  );
}

затем, после исправления опечаток, приведенных выше, вы сможете решить свою исходную проблему.

Опять же, как человек, который действительно может написать и понять приведенный выше код, Я настоятельно советую вам не использовать его . Хрупкий и опасный.

Почти никогда нет веских причин хранить обратные вызовы в тех местах, где вы не знаете, с какими аргументами вы будете их вызывать.

Должен быть отдельный тип и экземпляр CCC для каждого набора аргументов, с которым вы хотите вызвать его. 99/100 раз, когда люди задают этот вопрос, они задают неправильный вопрос.

0 голосов
/ 14 января 2019

C ++ - это типобезопасный язык. Это означает, что вы не можете делать именно то, что изложили в своем вопросе. Указатель на функцию, которая принимает определенные параметры, отличается от указателя на функцию, которая принимает другие параметры. Это фундаментально для C ++.

std::bind можно использовать для стирания разных типов к одному и тому же типу, но в конце вы получите один тип, который может быть вызван только с соответствующим набором параметров (если есть). Невозможно вызвать «базовую» связанную функцию с ее реальными параметрами. Это потому, что вся цель std::bind - заставить их исчезнуть и стать недоступными. Вот для чего std::bind.

У вас есть только ограниченный набор опций, чтобы сделать эту работу, оставаясь с границами и ограничениями безопасности типов C ++.

  1. Используйте void *, каким-то образом. На самом деле, нет. Не делай этого. Это только вызовет больше проблем и головной боли.

  2. Иметь отдельный список и классы обратных вызовов, по одному списку для каждого набора обратных вызовов, которые принимают определенный набор параметров. Вы должны знать, в момент вызова обратного вызова, какие параметры вы намереваетесь передать. Итак, просто получите ваш обратный вызов из соответствующего списка.

  3. Используйте std::variant. Безопасный тип std::variant предназначен только для C ++ 17 (но у boost есть похожий шаблон, который в основном эквивалентен и доступен со старыми ревизиями C ++). Все ваши обратные вызовы принимают один параметр std::variant, вариант каждого возможного набора параметров (обозначенный как std::tuple из них, или некоторый экземпляр класса / структуры). Каждый обратный вызов должен решить, что делать, если он получит std::variant, содержащий неверное значение параметра.

В качестве альтернативы std::variant может быть вариантом различных типов std::function, таким образом перенося ответственность за проверку типов на вызывающую сторону вместо каждого обратного вызова.

Суть в том, что C ++ по сути является типобезопасным языком; и это как раз одна из причин, по которой можно было бы использовать C ++ вместо другого языка, который не имеет такой же безопасности типов.

Но будучи языком безопасного типа, это означает, что у вас есть определенные ограничения, когда речь идет о смешивании разных типов. В частности: вы не можете. Все в C ++ всегда и должно быть одного типа.

...