c++ - todas - Cómo llamar a una función de plantilla si existe, y algo más de lo contrario?
tipos de plantillas en c++ (7)
¿No puedes usar la especialización completa aquí (o sobrecargar) en foo? Por ejemplo, tener la barra de llamadas de la plantilla de función, pero para ciertos tipos, ¿se especializa completamente para llamar a baz?
Quiero hacer algo como
template <typename T>
void foo(const T& t) {
IF bar(t) would compile
bar(t);
ELSE
baz(t);
}
Pensé que algo usando enable_if
haría el trabajo aquí, dividir foo
en dos partes, pero parece que no puedo resolver los detalles. ¿Cuál es la forma más simple de lograr esto?
Creo que la solución de litb funciona, pero es demasiado compleja. La razón es que está presentando una función fallback::bar(...)
que actúa como una "función de último recurso", y luego hace todo lo posible para NO llamarla. ¿Por qué? Parece que tenemos un comportamiento perfecto para ello:
namespace fallback {
template<typename T>
inline void bar(T const& t, ...)
{
baz(t);
}
}
template<typename T>
void foo(T const& t)
{
using namespace fallback;
bar(t);
}
Pero como indiqué en un comentario en la publicación original de litb, hay muchas razones por las cuales bar(t)
podría no compilarse, y no estoy seguro de que esta solución maneje los mismos casos. Ciertamente fallará en una private bar::bar(T t)
Hay dos búsquedas que se realizan para la bar
nombre. Una es la búsqueda no calificada del contexto de definición de foo
. La otra es la búsqueda dependiente de los argumentos en cada contexto de instanciación (pero el resultado de la búsqueda en cada contexto de instanciación no permite cambiar el comportamiento entre dos contextos de instanciación diferentes).
Para obtener el comportamiento deseado, puede ir y definir una función de respaldo en un espacio de nombres fallback
que devuelve algún tipo único
namespace fallback {
// sizeof > 1
struct flag { char c[2]; };
flag bar(...);
}
Se llamará a la función de bar
si nada más coincide porque la elipsis tiene el peor costo de conversión. Ahora, incluya los candidatos en su función mediante una directiva using de fallback
, de modo que fallback::bar
se incluya como candidato en la llamada a la bar
.
Ahora, para ver si una llamada a la bar
resuelve en su función, la llamará y verificará si el tipo de devolución es flag
. El tipo de devolución de una función elegida de otro modo podría ser nulo, por lo que debe realizar algunos trucos de operador de coma para evitarlo.
namespace fallback {
int operator,(flag, flag);
// map everything else to void
template<typename T>
void operator,(flag, T const&);
// sizeof 1
char operator,(int, flag);
}
Si nuestra función fue seleccionada, la invocación del operador de coma devolverá una referencia a int
. Si no es así o si la función seleccionada devuelve void
, entonces la invocación devuelve void
a su vez. Luego, la siguiente invocación con flag
como segundo argumento devolverá un tipo que tenga sizeof 1 si se seleccionó nuestro respaldo, y un tamaño de 1 mayor (se usará el operador de coma incorporado porque el void
está en la mezcla) si se seleccionó algo más .
Comparamos el tamaño de y delegamos a una estructura.
template<bool>
struct foo_impl;
/* bar available */
template<>
struct foo_impl<true> {
template<typename T>
static void foo(T const &t) {
bar(t);
}
};
/* bar not available */
template<>
struct foo_impl<false> {
template<typename T>
static void foo(T const&) {
std::cout << "not available, calling baz...";
}
};
template <typename T>
void foo(const T& t) {
using namespace fallback;
foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>
::foo(t);
}
Esta solución es ambigua si la función existente también tiene puntos suspensivos. Pero eso parece ser bastante improbable. Prueba usando la alternativa:
struct C { };
int main() {
// => "not available, calling baz..."
foo(C());
}
Y si se encuentra un candidato usando la búsqueda dependiente del argumento
struct C { };
void bar(C) {
std::cout << "called!";
}
int main() {
// => "called!"
foo(C());
}
Para probar la búsqueda no calificada en el contexto de la definición, definamos la siguiente función arriba de foo_impl
y foo
(coloque la plantilla foo_impl arriba de foo
, para que ambos tengan el mismo contexto de definición)
void bar(double d) {
std::cout << "bar(double) called!";
}
// ... foo template ...
int main() {
// => "bar(double) called!"
foo(12);
}
Si está dispuesto a limitarse a Visual C ++, puede usar las __if_exists y __if_not_exists .
Práctico en una pizca, pero específico de la plataforma.
litb te ha dado una muy buena respuesta . Sin embargo, me pregunto si, dado un contexto más amplio, no podríamos pensar en algo que sea menos genérico, pero también menos elaborado .
Por ejemplo, ¿qué tipos pueden ser T
? ¿Cualquier cosa? Algunos tipos? ¿Un conjunto muy restringido sobre el que tienes control? Algunas clases que diseñas junto con la función foo
? Dado lo último, podrías poner algo así como
typedef boolean<true> has_bar_func;
en los tipos y luego cambiar a diferentes sobrecargas foo
basadas en eso:
template <typename T>
void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
template <typename T>
void foo_impl(const T& t, boolean<false> /*has_bar_func*/);
template <typename T>
void foo(const T& t) {
foo_impl( t, typename T::has_bar_func() );
}
Además, ¿la función bar
/ baz
tiene casi cualquier firma, hay un conjunto restringido o solo hay una firma válida? Si esto último, la idea de repliegue (excelente) de litb, junto con una metafunción que emplea sizeof
podría ser un poco más simple. Pero esto no lo he explorado, así que es solo un pensamiento.
EDIT: ¡hablé demasiado pronto! La respuesta de litb muestra cómo esto se puede hacer realmente (a un posible costo de su cordura ... :-P)
Desafortunadamente, creo que el caso general de comprobar "would this compile" está fuera del alcance de la función de función argumento deducción + SFINAE , que es el truco habitual para este tipo de cosas. Creo que lo mejor que puedes hacer es crear una plantilla de función de "copia de seguridad":
template <typename T>
void bar(T t) { // "Backup" bar() template
baz(t);
}
Y luego cambie foo()
simplemente:
template <typename T>
void foo(const T& t) {
bar(t);
}
Esto funcionará para la mayoría de los casos. Como el tipo de parámetro de la plantilla bar()
es T
, se considerará "menos especializado" cuando se lo compara con cualquier otra función o plantilla de función llamada bar()
y cederá prioridad a esa función o plantilla de función preexistente durante la resolución de sobrecarga. Excepto eso:
- Si la
bar()
preexistentebar()
es en sí misma una plantilla de función que toma un parámetro de plantilla de tipoT
, surgirá una ambigüedad porque ninguna plantilla es más especializada que la otra y el compilador se quejará. - Las conversiones implícitas tampoco funcionarán y darán lugar a problemas difíciles de diagnosticar: supongamos que hay una
bar(long)
preexistentebar(long)
pero se llamafoo(123)
. En este caso, el compilador elegirá silenciosamente crear una instancia de la plantilla debar()
"de respaldo"bar()
conT = int
lugar de realizar la promociónint->long
, ¡aunque este último se habría compilado y funcionaría correctamente!
En resumen: no hay una solución fácil y completa, y estoy seguro de que ni siquiera hay una solución completa y complicada. :(
//default
//////////////////////////////////////////
template <class T>
void foo(const T& t){
baz(t);
}
//specializations
//////////////////////////////////////////
template <>
void foo(const specialization_1& t){
bar(t);
}
....
template <>
void foo(const specialization_n& t){
bar(t);
}