2009-08-26 8 views
15

tengo el siguiente problema:prioridad al elegir las funciones de plantilla sobrecargados en C++

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class Different 
{ 
}; 

class X 
{ 
public: 
    template <typename T> 
    static const char *func(T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

    static const char *func(Base *data) 
    { 
    // Do something specific... 
    return "Specific"; 
    } 
}; 

Si ahora hago

Derived derived; 
Different different; 
std::cout << "Derived: " << X::func(&derived) << std::endl; 
std::cout << "Different: " << X::func(&different) << std::endl; 

me sale

Derived: Generic 
Different: Generic 

Pero lo que yo quiero es que para todas las clases derivadas de Base se llama el método específico. Así, el resultado debería ser:

Derived: Specific 
Different: Generic 

¿Hay alguna manera de poder rediseñar el X: func (...) s para alcanzar este objetivo?

EDIT:

asumir que no es conocido por la persona que llama de X :: func (...) si la clase presentada como el parámetro se deriva de la base o no. Así que Casting to Base no es una opción. De hecho, la idea detrás de todo esto es que X :: func (...) debería 'detectar' si el parámetro se deriva de Base o no y llamar a un código diferente. Y por motivos de rendimiento, la "detección" debe realizarse en tiempo de compilación.

Respuesta

14

¡Encontré una solución MUY fácil!

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class Different 
{ 
}; 

class X 
{ 
private: 
    template <typename T> 
    static const char *intFunc(const void *, T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

    template <typename T> 
    static const char *intFunc(const Base *, T *data) 
    { 
    // Do something specific... 
    return "Specific"; 
    } 

public: 
    template <typename T> 
    static const char *func(T *data) 
    { 
    return intFunc(data, data); 
    } 
}; 

¡Esto funciona muy bien y es muy delgado! El truco consiste en dejar que el compilador seleccione el método correcto mediante el primer parámetro (de lo contrario, inútil).

+1

+1, debería haberlo pensado. Este truco se usa mucho en STL para seleccionar entre varias versiones de algoritmos basados ​​en la categoría de iteradores pasados. – avakar

+1

Sin embargo, tenga en cuenta la versión de Boost, funcionará con otros rasgos además de la convertibilidad. – avakar

+0

gracias por publicar, de hecho es una buena solución. – ttvd

0

Sólo Typecast derivada basar

X :: func ((Base *) & deriva)

funciona ....

+3

En C++, es preferible usar static_cast o dynamic_cast para transmitir estilos C. (c.f. Stevens) –

1

la expresión:

X::func(derived) 

Medios que el compilador generará una declaración y un código que efectivamente tiene esta firma:

static const char *func(Derived *data); 

el cual resulta ser un partido mejor que tu:

static const char *func(Base *data); 

La función de plantilla se utilizará para todo lo que es legal para nombretipo, por ejemplo, cualquier clase que utilice como T y excluirá de hecho la utilización de la versión Base, debido a la política de tiempo de compilación.

Mi sugerencia es utilizar specialization en X para sus tipos específicos, es decir .:

template <typename T> 
    static const char *func(T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

template <> 
    static const char *func(Derived *data) // 'Derived' is the specific type 
    { 
    // Do something specific... 
    return "Specific"; 
    } 

Esperanza que funciona!

+0

No, no funciona. – avakar

+0

No, eso no funciona. Acabo de probarlo con gcc. Tiene sentido ya que todavía obtiene una mejor coincidencia con la versión genérica. – Troubadour

+0

Whoops: destinado a especializarse como static const char * func (Derived * data) ... esos dos deberían ser al menos iguales en los ojos del compilador. Esperemos que elija el explícitamente especializado ... – jscharf

7

Debe usar SFINAE para esto. En el siguiente código, la primera función se puede instanciar si y solo si pasa algo que no se puede (implícitamente) convertir a Base *. La segunda función tiene esto invertido.

Es posible que desee leer en enable_if.

#include <iostream> 
#include <boost/utility/enable_if.hpp> 
#include <boost/type_traits.hpp> 

class Base {}; 
class Derived : public Base {}; 
class Different {}; 

struct X 
{ 
    template <typename T> 
    static typename boost::disable_if<boost::is_convertible<T *, Base *>, 
     const char *>::type func(T *data) 
    { 
     return "Generic"; 
    } 

    template <typename T> 
    static typename boost::enable_if<boost::is_convertible<T *, Base *>, 
     const char *>::type func(T *data) 
    { 
     return "Specific"; 
    } 
}; 

int main() 
{ 
    Derived derived; 
    Different different; 
    std::cout << "Derived: " << X::func(&derived) << std::endl; 
    std::cout << "Different: " << X::func(&different) << std::endl; 
} 
+0

Bueno, 'enable_if' es bastante fácil de implementar. Sin embargo, 'is_convertible' parece ser una historia bastante diferente. Personalmente, no me gustaría implementarlo por mi cuenta. – avakar

+0

Precisamente. Sin embargo, muchos de ellos parecen ser soluciones para varios compiladores no conformes. Tal vez hay una breve versión elegante escondida en ese archivo en alguna parte. Realmente no tengo ganas de buscarlo, y apuesto a que tampoco tú. Me gustaría ir con Boost :-) – avakar

+0

Sin embargo, tenga en cuenta que puede haber dos conceptos: convertibilidad de puntero y convertibilidad universal.La siguiente prueba de convertibilidad de puntero: Básicamente, es: 'plantilla struct is_ptr_convertible {static char (& is (B *)) [1]; carácter estático (y es (...)) [2]; valor de const bool estático = (sizeof es ((D *) 0) == 1); }; ' –

1

Si está utilizando impulso, puede hacerlo con alguna plantilla metaprogramming:

#include <boost/type_traits/is_base_of.hpp> 

class X 
{ 
private: 
    template <typename T> 
    static const char *generic_func(T *data) 
    { 
     // Do something generic... 
     return "Generic"; 
    } 

    template <typename T> 
    static const char *base_func(T *data) 
    { 
     // Do something specific... 
     return "Specific"; 
    } 

public: 
    template <typename T> 
    static const char* func(T* data) 
    { 
     if (boost::is_base_of<Base, T>::value) 
      return base_func(data); 

     return generic_func(data); 
    } 
}; 

La metafunción is_base_of se evalúa en tiempo de compilación y el optimizador muy probablemente eliminará la rama muerta del if en la función func. Este enfoque le permite tener más de un caso específico.

0

Estaba buscando establecer prioridades sobre la superposición de enable_if, específicamente para recurrir a llamar a los métodos de contenedor STL, donde mis rasgos eran cosas tales como is_assignable is_insterable, etc. con el que hay superposición en varios contenedores.

Quería dar prioridad a la asignación, si existiera, de lo contrario utilizaría un iterador de inserción. Este es un ejemplo genérico de lo que se me ocurrió (modificado con infinitos niveles de prioridad por algunas personas prácticas en el canal #boost irc). Funciona como la conversión implícita del nivel de prioridad clasifica la sobrecarga debajo de otra que, de lo contrario, es una opción igualmente válida, eliminando la ambigüedad.

#include <iostream> 
#include <string> 

template <std::size_t N> 
struct priority : priority<N - 1> {}; 

template <> 
struct priority<0> {}; 

using priority_tag = priority<2>; 

template <typename T> 
void somefunc(T x, priority<0>) 
{ 
    std::cout << "Any" << std::endl; 
} 

template <typename T> 
std::enable_if_t<std::is_pod<T>::value > 
somefunc(T x, priority<2>) 
{ 
    std::cout << "is_pod" << std::endl; 
} 

template <typename T> 
std::enable_if_t<std::is_floating_point<T>::value > 
somefunc(T x, priority<1>) 
{ 
    std::cout << "is_float" << std::endl; 
} 

int main() 
{ 
    float x = 1; 
    somefunc(x, priority_tag{}); 
    int y = 1; 
    somefunc(y, priority_tag{}); 
    std::string z; 
    somefunc(z, priority_tag{}); 
    return 0; 
} 

También se sugirió que en C++ 14 tan sólo pudiera utilizar constexpr if para lograr la misma cosa, que era mucho más limpio si Visual Studio 2015 los apoyó. Espero que esto ayude a alguien más.

#include <iostream> 
#include <string> 

template <typename T> 
void somefunc(T x) 
{ 
    if constexpr(std::is_floating_point<T>::value) { 
     static_assert(std::is_floating_point<T>::value); 
     std::cout << "is_float" << std::endl; 
    } else if constexpr(std::is_pod<T>::value) { 
     static_assert(std::is_pod<T>::value); 
     std::cout << "is_pod" << std::endl; 
    } else { 
     static_assert(!std::is_floating_point<T>::value); 
     static_assert(!std::is_pod<T>::value); 
     std::cout << "Any" << std::endl; 
    } 
} 

int main() 
{ 
    float x = 1; 
    somefunc(x); 
    int y = 1; 
    somefunc(y); 
    std::string z; 
    somefunc(z); 
    return 0; 
} 

// gracias a k-ballo @ #boost!

Cuestiones relacionadas