2012-02-14 17 views
7

Tengo funciones para convertir diferentes tipos aritméticos en un tipo de punto flotante de precisión medio (solo un uint16_t en el nivel más bajo) y tengo diferentes funciones para tipos de fuente de coma flotante y entera, usando SFINAE y std::enable_if :Diferenciación SFINAE entre firmado y no firmado

template<typename T> 
uint16_t to_half(typename std::enable_if< 
       std::is_floating_point<T>::value,T>::type value) 
{ 
    //float to half conversion 
} 

template<typename T> 
uint16_t to_half(typename std::enable_if< 
       std::is_integral<T>::value,T>::type value) 
{ 
    //int to half conversion 
} 

Estos se llaman internamente a partir de un constructor universal con plantilla de instancias explícita:

template<typename T> 
half::half(T rhs) 
    : data_(detail::conversion::to_half<T>(rhs)) 
{ 
} 

Esto compila y funciona muy bien. Ahora trato de diferenciar entre números enteros con y sin signo, mediante la sustitución de la segunda función con las dos funciones:

template<typename T> 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
       std::is_signed<T>::value,T>::type value) 
{ 
    //signed to half conversion 
} 

template<typename T> 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
       std::is_unsigned<T>::value,T>::type value) 
{ 
    //unsigned to half conversion 
} 

Pero una vez que trato de compilar este VS2010 me da

C2995 de error: "uint16_t math::detail::conversion::to_half(std::enable_if<std::tr1::is_integral<_Ty>::value && std::tr1::is_signed<_Ty>::value, T>::type)": Función plantilla ya definida.

Parece que no puede eliminar la ambigüedad entre las dos plantillas, pero obviamente no tuvo problemas con la versión integral junto con la versión de coma flotante.

Pero como no soy tan mago como plantilla, me puede estar faltando algo obvio aquí (o tal vez debería funcionar y es solo un error VS2010). Entonces, ¿por qué no funciona esto y cómo se puede hacer funcionar con la menor sobrecarga de programación posible y en los límites de las características de solo estándar (si es posible)?

+1

No está claro si 'is_signed' /' is_unsigned' es mutuamente exclusivo (hello 'char'?). Intenta hacer que la segunda versión diga '! Std :: is_signed :: value' en su lugar. –

+0

¿Puedes tratar de usar 'std :: is_signed :: value' para uno de los miembros y'! Std :: is_signed :: value' para el otro? Esto es solo para asegurarse de que no solo haya algún tipo que tenga configuraciones incoherentes para 'is_signed' y' is_unsigned'. –

+0

@KerrekSB & Dietmar Hah, ¡eso fue todo! No puedo creer que fue así de fácil. Si alguien lo agrega como respuesta lo aceptaré. –

Respuesta

3

Si esto no funciona, entonces su compilador tiene un error.

Dos expresiones con parámetros de plantilla se consideran equivalentes si dos definiciones de funciones incorporadas las expresiones podrían cumplir la norma una definición ...

Esa es la regla más importante a considerar aquí (a la izquierda en los detalles de "..."). Sus dos plantillas no satisfacen el ODR porque sus secuencias de token son diferentes.

Dos plantillas de función son equivalentes si se declaran en el mismo alcance, tener el mismo nombre, tienen listas de parámetros plantilla idénticos, y tienen tipos de retorno y listas de parámetros que son equivalentes usando las reglas descritas anteriormente para comparar las expresiones que implican parámetros de la plantilla.

De modo que sus dos plantillas definen diferentes plantillas y no entran en conflicto. Ahora puede verificar si sus plantillas son "funcionalmente equivalentes". Lo serían si para cualquier posible conjunto de argumentos de plantilla, su expresión enable_if siempre arrojaría el mismo valor. Pero dado que eso no es cierto para is_unsigned y is_signed, este tampoco es el caso. Si así fuera, entonces su código estaría mal formado, pero sin requerir un diagnóstico (lo que efectivamente significa "comportamiento indefinido").

+0

Reemplazar 'is_unsigned' con'! Is_signed' (o viceversa) funcionó, así que supongo que (aunque no es tan versado en las profundidades de la especificación del idioma, por no hablar de plantillas) que hay algún tipo que está firmado y designado. ¿O podría ser porque las versiones predeterminadas de esas plantillas se evalúan como 'false' para los tipos no aritméticos? Pero, de nuevo, también está el 'is_integral' para desambiguar las cosas (para los tipos integrales debería ser mutuamente exclusivo, ¿no?). –

+0

@Christian No tengo idea de lo que están haciendo, pero definitivamente es un comportamiento incorrecto. Pruebe '!! is_unsigned' en lugar de'! Is_signed'. No me sorprendería verlo "funcionando" también :) –

+0

Ja, eso funcionó, también. Ok, ahora creo que realmente se pone un poco ridículo. Probablemente tengas razón de que el compilador tiene la culpa aquí. –

7

Personalmente, yo evitaría SFINAE aquí tanto como sea posible ya que se puede lograr lo mismo con sobrecarga:

template<typename T> 
uint16_t to_half_impl(T val, std::true_type, std::true_type) 
{ 
    // is_integral + is_signed implementation 
} 

template<typename T> 
uint16_t to_half_impl(T val, std::true_type, std::false_type) 
{ 
    // is_integral + is_unsigned implementation 
} 

template<typename T> 
uint16_t to_half_impl(T val, std::false_type, std::true_type) 
{ 
    // is_floating_point implementation 
} 

template<typename T> 
typename std::enable_if<std::is_arithmetic<T>::value, uint16_t>::type to_half(T val) 
{ 
    return to_half_impl(val, std::is_integral<T>(), std::is_signed<T>()); 
} 
+0

+1 Buena alternativa. Pero como nota al margen, creo que el último argumento para la versión flotante debe ser 'std :: true_type', ya que los flotantes siempre están firmados (al menos las implementaciones habituales, para mi código de conversión no IEEE no funcionará de todos modos). –

+0

@Christian: Estás totalmente en lo correcto; Estaba basando la lógica de los documentos de MSDN para 'is_signed', que resulta ser incorrecta (sorpresa, sorpresa). Fijo. – ildjarn

1

El lenguaje más común es utilizar SFINAE en el tipo de retorno más que el argumento tipo. De lo contrario, el tipo de plantilla T no puede deducirse. Con

// from C++14 
template<bool C, typename T> using enable_if_t = typename std::enable_if<C,T>::type; 

template<typename T> 
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, uint16_t> 
to_half(T value) 
{ 
    //signed to half conversion 
} 

template<typename T> 
enable_if_t<std::is_integral<T>::value && !std::is_signed<T>::value, int16_t> 
to_half(T value) 
{ 
    //unsigned to half conversion 
} 

el tipo T en la siguiente declaración

auto y=to_half(x); // T is deduced from argument, no need for <T> 

es deducible (incluso trivial), pero para su código original no lo es! Hecho, cuando se ejecuta esta declaración con su aplicación a través to_half() sonido metálico da

test.cc:24:11: error: no matching function for call to 'to_half' 
    auto x= to_half(4); 
      ^~~~~~~ 
test.cc:7:10: note: candidate template ignored: couldn't infer template argument 'T' 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
     ^
test.cc:15:10: note: candidate template ignored: couldn't infer template argument 'T' 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
     ^

Por supuesto, si se proporciona explícitamente el argumento de plantilla (como lo hizo), no aparece este problema. Entonces, su código no estaba mal (pero sí el compilador), pero ¿de qué sirve SFINAE si pasa el tipo de argumento de la plantilla?

+0

¿No debería tener los mismos problemas de ambigüedad que SFINAE en el tipo de argumento? ¿Hay alguna regla que haga que esto funcione pero la versión del argumento no? De lo contrario, no parece abordar la pregunta demasiado. –

+0

Existe un ** muy buen motivo ** para no usar SFINAE en el tipo de argumento, vea la respuesta editada. – Walter

Cuestiones relacionadas