Scott Meyers publicó el contenido y el estado de su próximo libro EC ++ 11. Escribió que un elemento del libro podría ser "Evitar std::enable_if
en firmas de funciones" .
std::enable_if
puede usarse como argumento de función, como tipo de retorno o como plantilla de clase o parámetro de plantilla de función para eliminar condicionalmente funciones o clases de la resolución de sobrecarga.
En esta pregunta se muestran las tres soluciones.
Como parámetro de función:
template<typename T>
struct Check1
{
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, int>::value >::type* = 0) { return 42; }
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, double>::value >::type* = 0) { return 3.14; }
};
Como parámetro de plantilla:
template<typename T>
struct Check2
{
template<typename U = T, typename std::enable_if<
std::is_same<U, int>::value, int>::type = 0>
U read() { return 42; }
template<typename U = T, typename std::enable_if<
std::is_same<U, double>::value, int>::type = 0>
U read() { return 3.14; }
};
Como tipo de retorno:
template<typename T>
struct Check3
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
return 42;
}
template<typename U = T>
typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
return 3.14;
}
};
- ¿Qué solución debería preferirse y por qué debería evitar otras?
- ¿En qué casos "Evitar
std::enable_if
en firmas de funciones" se refiere al uso como tipo de retorno (que no es parte de la firma de función normal sino de las especializaciones de plantilla)? - ¿Hay alguna diferencia para las plantillas de funciones miembro y no miembro?
std::enable_if
saturar mis firmas de funciones (especialmente lanullptr
versión fea del argumento de función adicional ) porque siempre parece lo que es, un truco extraño (por algostatic if
poderoso) hacer mucho más hermoso y limpio) usando la plantilla black-magic para explotar una función de lenguaje interesante. Es por eso que prefiero el envío de etiquetas siempre que sea posible (bueno, todavía tiene argumentos extraños adicionales, pero no en la interfaz pública y también mucho menos feo y críptico ).=0
entypename std::enable_if<std::is_same<U, int>::value, int>::type = 0
realidad? No pude encontrar los recursos correctos para entenderlo. Sé la primera parte antes=0
tiene un tipo de miembroint
siU
yint
es el mismo. ¡Muchas gracias!Respuestas:
Pon el truco en los parámetros de la plantilla .
El
enable_if
enfoque del parámetro on template tiene al menos dos ventajas sobre los demás:legibilidad : el uso enable_if y los tipos return / argumento no se fusionan en una parte desordenada de desambigadores de nombres de tipos y accesos de tipos anidados; a pesar de que el desorden del desambigador y el tipo anidado se puede mitigar con plantillas de alias, eso aún fusionaría dos cosas no relacionadas. Enable_if use está relacionado con los parámetros de la plantilla, no con los tipos de retorno. Tenerlos en los parámetros de la plantilla significa que están más cerca de lo que importa;
Aplicabilidad universal : los constructores no tienen tipos de retorno y algunos operadores no pueden tener argumentos adicionales, por lo que ninguna de las otras dos opciones se puede aplicar en todas partes. Poner enable_if en un parámetro de plantilla funciona en todas partes ya que solo puedes usar SFINAE en plantillas de todos modos.
Para mí, el aspecto de legibilidad es el gran factor de motivación en esta elección.
fuente
FUNCTION_REQUIRES
macro aquí , hace que sea mucho más agradable de leer, y también funciona en los compiladores de C ++ 03, y depende de su usoenable_if
en el tipo de retorno. Además, el usoenable_if
de parámetros de plantilla de función causa problemas de sobrecarga, porque ahora la firma de la función no es única y causa errores de sobrecarga ambiguos.enable_if
un parámetro de plantilla no tipo predeterminado, que permite la sobrecarga. Es decir, enenable_if_t<condition, int> = 0
lugar detypename = enable_if_t<condition>
.flamingdangerzone
enlace en su comentario parece conducir a una página de instalación de spyware ahora. Lo marqué para la atención del moderador.std::enable_if
se basa en el principio " La falla de la subtítulos no es un error " (también conocido como SFINAE) durante la deducción de argumentos de plantilla . Esta es una característica de lenguaje muy frágil y debe tener mucho cuidado para hacerlo bien.enable_if
contiene una plantilla anidada o una definición de tipo (sugerencia: busque::
tokens), entonces la resolución de estos tipos o plantillas anidadas generalmente es un contexto no deducido . Cualquier falla de sustitución en un contexto no deducido es un error .enable_if
sobrecargas no pueden tener solapamiento porque la resolución de sobrecarga sería ambigua. Esto es algo que usted, como autor, necesita verificar usted mismo, aunque obtendrá buenas advertencias del compilador.enable_if
manipula el conjunto de funciones viables durante la resolución de sobrecarga que puede tener interacciones sorprendentes dependiendo de la presencia de otras funciones que se obtienen de otros ámbitos (por ejemplo, a través de ADL). Esto lo hace no muy robusto.En resumen, cuando funciona funciona, pero cuando no funciona puede ser muy difícil de depurar. Una muy buena alternativa es usar el despacho de etiquetas , es decir, delegar a una función de implementación (generalmente en un
detail
espacio de nombres o en una clase auxiliar) que recibe un argumento ficticio basado en la misma condición de tiempo de compilación que usa en elenable_if
.El despacho de etiquetas no manipula el conjunto de sobrecarga, pero lo ayuda a seleccionar exactamente la función que desea al proporcionar los argumentos adecuados a través de una expresión en tiempo de compilación (por ejemplo, en un rasgo de tipo). En mi experiencia, esto es mucho más fácil de depurar y acertar. Si es un aspirante a escritor de bibliotecas de rasgos de tipo sofisticados, es posible que necesite de
enable_if
alguna manera, pero para el uso más habitual de las condiciones de tiempo de compilación no se recomienda.fuente
enable_if
hacerlo bien?is_f_able
es algo que considero una tarea para los escritores de bibliotecas que, por supuesto, pueden usar SFINAE cuando eso les da una ventaja, pero para los usuarios "normales" y con un rasgois_f_able
, creo que el envío de etiquetas es más fácil.El parámetro de plantilla
Se puede usar fácilmente de manera incorrecta y produce errores con sobrecargas:
Aviso en
typename = std::enable_if_t<cond>
lugar de correctostd::enable_if_t<cond, int>::type = 0
tipo de retorno:
Por último, en el parámetro de función:
+
,-
,*
, ...)void* = nullptr
) (por lo que el puntero de la función sería diferente, etc.)Hay diferencias sutiles con la herencia y
using
:De acuerdo con el
using-declarator
(énfasis mío):namespace.udecl
Entonces, tanto para el argumento de plantilla como para el tipo de retorno, los métodos están ocultos en el siguiente escenario:
Demostración (gcc encuentra erróneamente la función base).
Mientras que con argumento, un escenario similar funciona:
Manifestación
fuente