Как создать массив указателей на функции разных прототипов? - PullRequest
0 голосов
/ 05 декабря 2018

У меня есть несколько функций, определенных следующим образом:

ParentClass*    fun1();
ParentClass*    fun2();
ParentClass*    fun3(bool inp=false);
ChildClass*     fun4();
ChildClass*     fun5(int a=1, int b=3);

Я хотел бы поместить их в какой-то массив следующим образом:

void* (*arr[5])() = {
    (void* (*)())fun1,
    (void* (*)())fun2,
    (void* (*)())fun3,
    (void* (*)())fun4,
    (void* (*)())fun5
}

Теперь я хотел быиспользуйте этот массив функций просто как

for(int i=0; i<5; i++)
    someFunction(arr[i]());

. Теперь я понимаю, что проблема заключается в void* (*arr[5])(), но, учитывая, что я хочу использовать только функции без предоставления аргумента, я бы хотел, чтобы все онибыть частью одного и того же массива.

Хотя это очень C-стиль.Есть ли лучший способ сделать это с помощью шаблонов в C ++?

Ответы [ 5 ]

0 голосов
/ 05 декабря 2018

A решение:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }


template<auto f, class R, class...Args>
struct explicit_function_caster {
  using Sig=R(Args...);
  using pSig=Sig*;
  constexpr operator pSig()const {
    return [](Args...args)->R {
      return static_cast<R>(f(std::forward<Args>(args)...));
    };
  }
};

template<auto f>
struct overload_storer_t {
  template<class R, class...Args>
  constexpr (*operator R() const)(Args...) const {
    return explicit_function_caster<f, R, Args...>{};
  }
  template<class...Args>
  auto operator()(Args&&...args)
  RETURNS( f( std::forward<Args>(args)... ) )
};
template<auto f>
overload_storer_t<f> generate_overloads={};

#define OVERLOADS_OF(...) \
  generate_overloads< \
    [](auto&&...args) \
    RETURNS( __VA_ARGS__( decltype(args)(args)... ) ) \
  >

, что довольно много, но дает нам:

ParentClass* (*arr[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};
void (*arr2[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};

в основном generate_overloads<x> занимаетconstexpr вызываемый объект x и позволяет вам приводить его во время компиляции к указателю на функцию любой совместимой подписи и вызывать его с (почти) любой подписью.

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

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

0 голосов
/ 05 декабря 2018

Поскольку вы пометили вопрос с C ++ 14, вы не должны использовать указатели функций!

С C ++ 14 вы должны предпочесть std::function и лямбды.

Также не следуетиспользуйте массив в стиле C, но только std::array и / или std::vector.

Также избегайте необработанных указателей, используйте std::unique_ptr и std::shared_ptr.

Самый простой и лучший способ решенияэто:

std::array<std::function<ParentClass*()>,5> arr {
    []() { return fun1(); },
    []() { return fun2(); },
    []() { return fun3(true); },
    []() { return fun4(); },
    []() { return fun5(7, 9); }
};

Почему бы не простой массив указателей, как в ответе @Quentin?Он использовал лямбду, но он не может использовать лямбду, которая связывает что-либо (если вам нужно).

0 голосов
/ 05 декабря 2018

Вы можете использовать std::bind

std::function<ParentClass *(void)> arr[5] = {
    std::bind(&fun1),
    std::bind(&fun2),
    std::bind(&fun3, false),
    std::bind(&fun4),
    std::bind(&fun5, 1, 3)
};

Теперь вы можете сделать

for(int i=0; i<5; i++)
    arr[i]();

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

Это также хорошо работает с функциями-членами.Вам просто нужно привязать ссылку на объект (например, this) в качестве первого параметра.

0 голосов
/ 05 декабря 2018

но с учетом того, что я хочу использовать функции только без указания аргумента

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

Это потому, чтопараметры по умолчанию на самом деле не «встроены» в функцию, а используются компилятором для дополнения вызова функции этими параметрами в месте вызова, где эти параметры опущены. (РЕДАКТИРОВАТЬ: Кроме того, как @Aconcagua так остро заметил в комментарии, так как параметры по умолчанию обычно определяются как часть объявления функции заголовка, любое изменение значений по умолчанию требует полной перекомпиляции любого модуля компиляции, который включал эти заголовки, ergo объявления функций, чтобы изменения действительно вступили в силу!)

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

Во всяком случае вам придется связать указатель функции вместе с набором параметров по умолчанию в некотором типе, который абстрагирует вызывающий, предоставляет параметры и снаружи предлагает полиморфный интерфейс.Таким образом, у вас будет std::vector<function_binder> или function_binder[], где связыватель функции имеет operator(), который вызывает функцию.

Но когда вы делаете связывание в первую очередь, вы можете связать его ванонимная функция, т.е. лямбды.Во время лямбда-инстанции параметры по умолчанию связаны.

std::vector<void(*)()> fvec = {
    []{ func0(); },
    []{ func1(); },
    []{ func2(); },
}
0 голосов
/ 05 декабря 2018

C-стиль или нет, то, что у вас есть, это прямое неопределенное поведение.Используйте lambdas:

void (*arr[5])() = {
    [] { fun1(); },
    [] { fun2(); },
    [] { fun3(); },
    [] { fun4(); },
    [] { fun5(); }
};

Это нормально, потому что они выполняют вызов через правильный тип функции и сами могут быть преобразованы в void (*)().

Пересылка возвращаемого значения остается достаточно простой, посколькулямбда обеспечивает контекст для преобразования.В вашем случае, поскольку ChildClass предположительно наследуется от ParentClass, достаточно неявного преобразования:

ParentClass *(*arr[5])() = {
    []() -> ParentClass * { return fun1(); },
    []() -> ParentClass * { return fun2(); },
    []() -> ParentClass * { return fun3(); },
    []() -> ParentClass * { return fun4(); },
    []() -> ParentClass * { return fun5(); }
};
...