c++ - Especialización de la función de plantilla parcial con enable_if: hacer implementación predeterminada
c++11 sfinae (3)
La función no puede ser parcialmente especializada. ¿Supongo que lo que quiere hacer es preferir aquellas sobrecargas que contienen una condición explícita? Una forma de lograrlo es mediante el uso de argumentos variad de puntos suspensivos en la declaración de la función default
, ya que la función de puntos suspensivos tiene menor prioridad en el orden de resolución de sobrecarga:
#include <iostream>
template<typename T>
void dummy_impl(T t, ...)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Floating point: " << t << std::endl;
}
template <class T>
void dummy(T t) {
dummy_impl(t, int{});
}
int main() {
dummy(5);
dummy(5.);
dummy("abc");
}
Salida:
Integral: 5
Floating point: 5
Generic: abc
Otra opción como @doublep se menciona en el comentario es mediante el uso de la estructura con la implementación de su función y luego especializarla parcialmente.
Uso de enable_if
C ++ 11 Quiero definir varias implementaciones especializadas para una función (según el tipo de parámetro, por ejemplo), así como una implementación predeterminada. ¿Cuál es la forma correcta de definirlo?
El siguiente ejemplo no funciona como se esperaba, ya que se llama a la implementación "genérica", sea cual sea el tipo T
#include <iostream>
template<typename T, typename Enable = void>
void dummy(T t)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
std::cout << "Floating point: " << t << std::endl;
}
int main() {
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Generic: 5"
}
Una solución en mi ejemplo mínimo consiste en declarar explícitamente la implementación "genérica" como no para tipos integrales ni de punto flotante, usando
std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type
Esto es exactamente lo que quiero evitar, ya que en mis casos de uso reales hay muchas implementaciones especializadas y me gustaría evitar una condición muy larga (¡propensa a errores!) Para la implementación predeterminada.
Puede introducir un rank
para dar prioridad a algunas de sus sobrecargas:
template <unsigned int N>
struct rank : rank<N - 1> { };
template <>
struct rank<0> { };
A continuación, puede definir sus sobrecargas dummy
esta manera:
template<typename T>
void dummy(T t, rank<0>)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Floating point: " << t << std::endl;
}
Luego, puede ocultar la llamada detrás de un dispatch
:
template <typename T>
void dispatch(T t)
{
return dummy(t, rank<1>{});
}
Uso:
int main()
{
dispatch(5); // Print "Integral: 5"
dispatch(5.); // Print "Floating point: 5"
dispatch("hi"); // Print "Generic: hi"
}
Explicación:
El uso de rank
introduce "prioridad" porque se requieren conversiones implícitas para convertir un rank<X>
a un rank<Y>
cuando X > Y
dispatch
primero intenta llamar al dummy
con rank<1>
, dando prioridad a sus sobrecargas restringidas. Si enable_if
falla, el rank<1>
se convierte implícitamente al rank<0>
y entra en el caso de "reserva".
Bono: aquí hay una implementación de C ++ 17 usando if constexpr(...)
.
template<typename T>
void dummy(T t)
{
if constexpr(std::is_integral_v<T>)
{
std::cout << "Integral: " << t << std::endl;
}
else if constexpr(std::is_floating_point_v<T>)
{
std::cout << "Floating point: " << t << std::endl;
}
else
{
std::cout << "Generic: " << t << std::endl;
}
}
Yo usaría la etiqueta de envío como así:
namespace Details
{
namespace SupportedTypes
{
struct Integral {};
struct FloatingPoint {};
struct Generic {};
};
template <typename T, typename = void>
struct GetSupportedType
{
typedef SupportedTypes::Generic Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
{
typedef SupportedTypes::Integral Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
{
typedef SupportedTypes::FloatingPoint Type;
};
template <typename T>
void dummy(T t, SupportedTypes::Generic)
{
std::cout << "Generic: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::Integral)
{
std::cout << "Integral: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::FloatingPoint)
{
std::cout << "Floating point: " << t << std::endl;
}
} // namespace Details
Y luego esconde el código de la placa de la caldera así:
template <typename T>
void dummy(T t)
{
typedef typename Details::GetSupportedType< T >::Type SupportedType;
Details::dummy(t, SupportedType());
}
GetSupportedType
le ofrece una forma central de adivinar el tipo real que va a utilizar, que es la que desea especializar cada vez que agregue un nuevo tipo.
Luego simplemente invoca la sobrecarga dummy
correcta proporcionando una instancia de la etiqueta correcta.
Por último, invocar dummy
:
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Floating point: 5"
dummy("lol"); // Print "Generic: lol"