2009-09-06 8 views
32

quiero hacer algo como¿Cómo llamar a una función de plantilla si existe, y algo más?

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í, división foo en dos pedazos, pero me parece que no puede trabajar en los detalles. ¿Cuál es la forma más simple de lograr esto?

+0

Buena pregunta. +1 –

Respuesta

31

Hay dos búsquedas que se realizan para el nombre bar. Una de ellas es la búsqueda sin reservas en el contexto de la definición foo. El otro es dependiente de búsqueda de discusión en cada contexto de instancias (pero el resultado de la búsqueda en cada instanciación contexto no se le permite cambiar el comportamiento entre dos contextos de creación de instancias diferentes).

para obtener el comportamiento deseado, se podía 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(...); 
} 

elSe llamará a la funciónsi 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 de uso de fallback, de modo que fallback::bar se incluya como candidato en la llamada al bar.

Ahora, para ver si una llamada al bar resuelve 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 se ha seleccionado la función de nuestra entonces la invocación operador coma devolverá una referencia a int. Si no es así o si la función seleccionada devolvió void, la invocación devuelve void por turno. A continuación, 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 mayor 1 (el operador de coma incorporado se usará porque void está en la mezcla) si se seleccionó algo más .

Comparamos el tamaño de y lo delegamos en 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 utilizando el repliegue:

struct C { }; 
int main() { 
    // => "not available, calling baz..." 
    foo(C()); 
} 

Y si un candidato se encuentra el uso de las operaciones de búsqueda depende argumento

struct C { }; 
void bar(C) { 
    std::cout << "called!"; 
} 
int main() { 
    // => "called!" 
    foo(C()); 
} 

Para probar las operaciones de búsqueda no cualificada en el contexto definición, vamos a definir la siguiente función anterior foo_impl y foo (poner el foo_impl foo plantilla anterior, por lo que tienen ambos el mismo contexto definición)

void bar(double d) { 
    std::cout << "bar(double) called!"; 
} 

// ... foo template ... 

int main() { 
    // => "bar(double) called!" 
    foo(12); 
} 
+0

¡Muy bien! Esto funciona bien, ¡y es bastante elegante! –

+5

¡Santa mierda! O_o ¿De dónde sacaste la idea de usar el operador de coma de esa manera? (Descubrir lo que hiciste allí tomó 15 minutos solo ...) –

+0

BTW Quiero decir "¡Mierda!" en el buen sentido :) –

0

¿No puede usar la especialización completa aquí (o la sobrecarga) 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?

+2

no todas las propiedades se someten a "si se compila", la verificación se puede verificar con la inferencia de tipo que ocurre cuando se llama a una función de plantilla. –

2

EDIT: ¡hablé demasiado pronto! litb's answer muestra cómo en realidad esto puede ser hecho (aún a costa de su cordura ... :-P)

Desafortunadamente, creo que el caso general de comprobación "haría esta compilación" está fuera del alcance de los function template argument deduction + SFINAE, que es el truco habitual para esto. Creo que lo mejor que puede hacer es crear una "copia de seguridad" plantilla de función:

template <typename T> 
void bar(T t) { // "Backup" bar() template 
    baz(t); 
} 

Y luego cambiar a simplemente foo():

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" en comparación con cualquier otra función o plantilla de función denominada bar() y, por lo tanto, cederá prioridad a esa función o plantilla de función preexistente durante la resolución de sobrecarga. Excepto que:

  • Si el preexistente bar() es en sí misma una plantilla de función que toma un parámetro de plantilla de tipo T, surgirá una ambigüedad porque ni plantilla es más especializado que el otro, 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 un bar(long) preexistente pero se llama a foo(123). En este caso, el compilador elegirá silenciosamente crear una instancia de la "copia de seguridad" de la plantilla bar() con T = int en lugar de realizar la promoción int->long, ¡aunque esta última se habría compilado y funcionaría correctamente!

En resumen: no hay una solución fácil y completa, y estoy bastante seguro de que ni siquiera hay una solución completa complicada. :(

2

Si usted es w Para limitarse a Visual C++, puede usar las declaraciones __if_exists y __if_not_exists.

Práctico en una pizca, pero específico de la plataforma.

+1

Sin embargo, eso no parece considerar si la llamada realmente compilaría. Considere una llamada a "foo (1)" pero existe una función "bar (char *)". –

+0

Interesante, no sabía sobre eso. +1 (aunque la crítica de litb es correcta). –

+0

hmm - wow, no tenía idea de que existieran - ¡gracias! –

6

litb has given you a very good answer. Sin embargo, me pregunto si, dado un contexto más amplio, no podríamos encontrar algo que sea menos genérico, sino también menos, um, 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? Teniendo en cuenta este último, podría sencillo poner algo como

typedef boolean<true> has_bar_func; 

en los tipos y luego cambiar a diferentes foo sobrecargas sobre la base de que:

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, puede la función bar/baz tener casi cualquier firma, ¿hay algún 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.

+0

+1 Buen punto, creo. Primero debería pensar en esto antes de hacer cosas demasiado complicadas. Usar ADL like con la función 'swap' podría ser útil también. De todos modos, gracias por los elogios :) –

+0

Bueno, el conjunto de 'T' que tiene una' barra' no es * completamente * arbitrario, pero es más que unos pocos tipos. Además, la especialización de 'has_bar_func' para los diferentes tipos sería una duplicación de código (pequeña, pero significativa). Y aunque la idea de litb es muy inteligente, no es tan "elaborada" (hay muchos ejemplos en el impulso que son significativamente más complicados, creo) –

+1

@litb: Cuando vi el 'struct flag {char c [2]; }; 'en la parte superior de una respuesta exhaustiva, supe que era tuya, aunque tu nombre aún no estaba visible en la pantalla. ':)' – sbi

2
//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); 
    } 
+0

+1 por pura simplicidad, pero al igual que con la solución de sbi, el problema aquí es que necesita mantener la lista de especializaciones actualizadas con la lista de tipos que admiten 'bar()' - y * que * es qué el OP está tratando de evitar hacerlo. –

+0

, pero uno lo hace igual que un handle the free standing std :: ostream & operator << (std :: ostream &, const classX &) - lo pones en claseX. Cuando uno dice "la lista" uno casi presupone que esa es la organización fuente.Me doy cuenta de que esto distribuye el conocimiento de foo, pero eso es mejor que confiar en las conversiones implícitas para "si compilará". Eso es un poco sospechoso por dos razones. – pgast

+0

1) ¿quién puede decir que "si se compilará" realmente obtiene el código que realmente quiere? 2) los cambios fuera de las clases pueden cambiar la semántica del código objetivo sin ningún tipo de indicación (agregar un constructor a una clase eliminar un constructor de otra). También poner las especializaciones explícitas en un encabezado podría agregar ambigüedades y contribuir a 2) arriba. – pgast

3

Creo que la solución de litb funciona, pero es demasiado compleja. La razón es que él está introduciendo 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 he indicado en un comentario al post original de litb, hay muchas razones por las bar(t) podían dejar de compilar, y no estoy seguro de que esta solución se encarga de la mismos casos. Ciertamente fallará en un private bar::bar(T t)

+2

Sin embargo, tiene el mismo comportamiento que el código de j_random_hacker. La elipsis no se considera al clasificar la sobrecarga con otras funciones si no hay un argumento correspondiente que haya pasado a la elipsis. ('13.3.2',' 13.3.3'). –

+0

+1 por mencionar el caso de falla de verificación de acceso. –

Cuestiones relacionadas