Как создать массив указателей на функции разных прототипов? - программирование
Подтвердить что ты не робот

Как создать массив указателей на функции разных прототипов?

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

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++?

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Это потому, что параметры по умолчанию на самом деле не "внедрены" в функцию, а используются компилятором для увеличения вызова функции с этими параметрами в вызывающем местоположении, где эти параметры опущены. (EDIT: Кроме того, поскольку @Aconcagua так остро наблюдается в комментарии, поскольку параметры по умолчанию обычно определяются как часть объявления функции заголовка, любое изменение значений по умолчанию требует полной перекомпиляции любого блока компиляции, который включал эти заголовки, функцию эрго декларации, чтобы изменения действительно вступили в силу!)

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

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

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

std::vector<void(*)()> fvec = {
    []{ func0(); },
    []{ func1(); },
    []{ func2(); },
}

Ответ 3

Вы можете использовать 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) в качестве первого параметра.

Ответ 4

Решение :

#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 и позволяет constexpr его во время компиляции указателю на функцию любой совместимой сигнатуры и называть ее (почти) любой сигнатурой.

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

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

Ответ 5

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

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

Также не следует использовать массив в стиле 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? Он использовал лямбду, но он не может использовать лямбду, которая связывает что-либо (если вам нужно).