c++ - implement - ¿Cómo creo una matriz de punteros de función de diferentes prototipos?
how to implement classes in c++ (5)
Tengo algunas funciones definidas así:
ParentClass* fun1();
ParentClass* fun2();
ParentClass* fun3(bool inp=false);
ChildClass* fun4();
ChildClass* fun5(int a=1, int b=3);
Me gustaría ponerlos en una serie de la siguiente manera:
void* (*arr[5])() = {
(void* (*)())fun1,
(void* (*)())fun2,
(void* (*)())fun3,
(void* (*)())fun4,
(void* (*)())fun5
}
Ahora me gustaría usar esta serie de funciones simplemente como
for(int i=0; i<5; i++)
someFunction(arr[i]());
Ahora me doy cuenta de que el problema es void* (*arr[5])()
, pero dado que solo quiero usar las funciones sin proporcionar un argumento, me gustaría que todas estas sean parte de la misma matriz.
Sin embargo, estas son formas de estilo muy C para hacerlo. ¿Hay una mejor manera de hacerlo usando plantillas en C ++?
pero dado que solo quiero usar las funciones sin proporcionar un argumento
Simplemente no funciona así. ¿Alguna vez se preguntó, entonces, cuando coloca declaraciones de funciones en un encabezado, por qué tiene que escribir parámetros predeterminados en el encabezado y no puede colocarlo en la definición en el archivo fuente de implementación?
Esto se debe a que, de hecho, los parámetros predeterminados no están "integrados" en la función, pero el compilador los utiliza para aumentar una llamada de función con esos parámetros en una ubicación de llamada, donde se omiten esos parámetros. (EDITAR: también, como @Aconcagua observó tan agudamente en un comentario, dado que los parámetros predeterminados se definen generalmente como parte de una declaración de función de encabezado, cualquier cambio de los valores predeterminados requiere una recompilación completa de cualquier unidad de compilación que incluya esos encabezados, función ergo Declaraciones, para que el cambio surta efecto!)
Si bien es perfectamente posible hacer una locura de conversión de tipo realmente extraña para construir una serie de punteros de función como esa, eventualmente tendrá que volver a la firma de llamada de función original para no invocar un comportamiento indefinido.
En todo caso, deberá enlazar el puntero a la función, junto con un conjunto de parámetros predeterminados en algún tipo que abstraiga la llamada, sí proporciona los parámetros y al exterior ofrece una interfaz polimórfica. Así que tendrías un std::vector<function_binder>
o function_binder[]
donde la carpeta de funciones tiene un operator()
que llama a la función.
Pero cuando estás haciendo un enlace en primer lugar, puedes hacerlo en una función anónima, es decir, lambdas. En el momento de la instancia lambda, los parámetros predeterminados están vinculados.
std::vector<void(*)()> fvec = {
[]{ func0(); },
[]{ func1(); },
[]{ func2(); },
}
Estilo C o no, lo que tienes es un comportamiento directo e indefinido. Use lambdas:
void (*arr[5])() = {
[] { fun1(); },
[] { fun2(); },
[] { fun3(); },
[] { fun4(); },
[] { fun5(); }
};
Estos están bien porque realizan la llamada a través del tipo correcto de la función, y ellos mismos son convertibles a void (*)()
.
El reenvío del valor devuelto es lo suficientemente simple, ya que la lambda proporciona un contexto para la conversión. En su caso, dado que ChildClass
supuestamente se hereda de ParentClass
, una conversión implícita es suficiente:
ParentClass *(*arr[5])() = {
[]() -> ParentClass * { return fun1(); },
[]() -> ParentClass * { return fun2(); },
[]() -> ParentClass * { return fun3(); },
[]() -> ParentClass * { return fun4(); },
[]() -> ParentClass * { return fun5(); }
};
Puedes usar 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)
};
ahora puedes hacerlo
for(int i=0; i<5; i++)
arr[i]();
Debe asegurarse de que todos los parámetros de función de todas las funciones estén vinculados.
Esto también funciona bien con las funciones miembro. Solo tiene que enlazar la referencia del objeto (por ejemplo, this
) como primer parámetro.
Una solución c ++ 20 :
#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)... ) ) /
>
que es un montón de repetitivo, pero nos pone:
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)
};
básicamente constexpr
generate_overloads<x>
toma un objeto llamable constexpr
x
y le permite convertirlo en tiempo de compilación en un puntero a una función de cualquier firma compatible y llamarlo con (casi) cualquier firma.
Mientras tanto, OVERLOADS_OF
convierte un nombre de función en un objeto constexpr
que realiza una resolución de sobrecarga en ese nombre de función. Lo uso aquí porque fun3
como puntero a una función no conoce sus argumentos predeterminados, pero en el momento de resolución de sobrecarga lo hace.
En este caso particular, es mucho más fácil simplemente escribir lambdas de juguete para hacer este trabajo; esto es solo un intento de automatizar la escritura de esas lambdas de juguete para firmas compatibles arbitrarias.
Ya que ha etiquetado la pregunta con C ++ 14, ¡no debe usar punteros de función!
Con C ++ 14 deberías preferir std::function
y lambadas.
Además, no debe usar la matriz de estilo C, sino solo std::array
y / o std::vector
.
También evite los punteros en bruto, use std::unique_ptr
y std::shared_ptr
.
Tan simple y el mejor whay para resolverlo es:
std::array<std::function<ParentClass*()>,5> arr {
[]() { return fun1(); },
[]() { return fun2(); },
[]() { return fun3(true); },
[]() { return fun4(); },
[]() { return fun5(7, 9); }
};
¿Por qué no un simple conjunto de punteros como en la respuesta @Quentin? Usó lambdas, pero no puede usar lambda, que se une a cualquier cosa (si es necesario).