Me gustaría usar el reenvío perfecto en una función con plantilla para garantizar que se conserve el valor l o el valor r, pero al mismo tiempo me gustaría imponer ciertas restricciones sobre los posibles tipos de parámetros.

Supongamos, por ejemplo, que solo quiero restringir T a un objeto invocable. El siguiente enfoque me parece correcto

template<typename T>
typename std::enable_if<std::is_callable<T>>::value>::type myFun(T&& val) 
{
    foo(std::forward<T>(val));
} 

Pero como todavía no me siento completamente cómodo con los diversos matices del SFINAE, agradecería los comentarios de otra persona.

¿Sería esta la forma correcta e idiomática de hacerlo?

0
Emerald Weapon 17 ene. 2017 a las 16:59
¿Por qué no especializar la plantilla para Allowed_Type y deshabilitar la plantilla general?
 – 
wally
17 ene. 2017 a las 17:00
¿Sería esa una mejor solución?
 – 
Emerald Weapon
17 ene. 2017 a las 17:03
2
(Casi) siempre prefiere la sobrecarga cuando desea manejar un tipo específico.
 – 
AndyG
17 ene. 2017 a las 17:04
@AndyG: De acuerdo, solo tendría dos sobrecargas aquí, void myFun(const Allowed_Type&) y void myFun(Allowed_Type&&) y ninguna plantilla.
 – 
Kerrek SB
17 ene. 2017 a las 17:10
Si quisiera buscar is_callable en su lugar, ¿lo harías de manera diferente?
 – 
Emerald Weapon
17 ene. 2017 a las 17:20

1 respuesta

La mejor respuesta

std::is_callable es una característica de C ++ 17, por cierto.

De todos modos, dado que su función myFun no devuelve nada, es un poco extraño poner enable_if como valor de retorno. Sí, se convertirá en void cuando se seleccione la plantilla, pero probablemente sea más legible colocarlo como un argumento de plantilla predeterminado:

template<typename T, std::enable_if_t<std::is_callable_v<T()>, int> = 0>
void myFun(T&& val) 
{
    foo(std::forward<T>(val));
}

Demo

Aunque sería un poco extraño que tu enable_if existiera en el vacío. Como mínimo, deberíamos detectar todos los tipos que no encajan y proporcionar al usuario un buen error de compilación a través de static_assert:

template<typename T, std::enable_if_t<std::is_callable_v<T()>, int> =0>
void myFun(T&& val) 
{
    foo(std::forward<T>(val));
}

template<typename T, std::enable_if_t<!std::is_callable_v<T()>, int> =0>
void myFun(T&& val) 
{
    static_assert(sizeof(T) == 0, "myFun is only callable with an argument that can be called as a function with no params");
}

Demo2

3
AndyG 17 ene. 2017 a las 18:17
Excepto por el problema de que MSVC no funciona y no lo entiende, std::enable_if_t<std::is_callable<T()>{},int> = 0 tiene mejores propiedades de resolución de sobrecarga que typename = std::enable_if_t<std::is_callable<T()>{}>.
 – 
Yakk - Adam Nevraumont
17 ene. 2017 a las 17:41
@Yakk: Actualizado, gracias. ¿Existe alguna preferencia entre std::enable_if_t<std::is_callable<T()>{},int> = 0 y template<typename T, std::enable_if_t<std::is_callable<T()>::value, int> = 0> además del hecho de que en el primero habrá una conversión implícita a bool y no es necesario marcar ::value?
 – 
AndyG
17 ene. 2017 a las 17:50
::value tiene 5 caracteres más que {}. Alternativamente, std::is_callable_v<T()> dado que este es un rasgo de C ++ 17 también es tan corto.
 – 
Yakk - Adam Nevraumont
17 ene. 2017 a las 18:10
Entonces, ¿por qué usar enable_if? Solo static_assert sería suficiente para fallar si el tipo no coincide con las condiciones.
 – 
Jean-Bernard Jansen
17 ene. 2017 a las 19:20
@ Jean-BernardJansen: Quitar enable_if en la segunda definición hace que una llamada de función sea ambigua para los tipos que satisfacen std::is_callable
 – 
AndyG
17 ene. 2017 a las 19:30