Metaprogtwigción: falla de la definición de función define una función separada

En esta respuesta , defino una plantilla basada en la propiedad is_arithmetic del tipo:

 template enable_if_t<is_arithmetic::value, string> stringify(T t){ return to_string(t); } template enable_if_t<!is_arithmetic::value, string> stringify(T t){ return static_cast(ostringstream() << t).str(); } 

dyp sugiere que, en lugar de la propiedad is_arithmetic del tipo, si to_string se define para el tipo, sean los criterios de selección de la plantilla. Esto es claramente deseable, pero no sé una manera de decir:

Si std::to_string no está definido, utilice la sobrecarga de ostringstream .

Declarar el criterio to_string es simple:

 template decltype(to_string(T{})) stringify(T t){ return to_string(t); } 

Es lo opuesto a ese criterio que no puedo entender cómo construirlo. Obviamente, esto no funciona, pero espero que transmita lo que bash construir:

 template enable_if_t (T t){ return static_cast(ostringstream() << t).str(); } 

Recién votado en los fundamentos de la biblioteca TS en la reunión del comité de la semana pasada:

 template using to_string_t = decltype(std::to_string(std::declval())); template using has_to_string = std::experimental::is_detected; 

A continuación, etiquete el envío y / o SFINAE en has_to_string al contenido de su corazón.

Puede consultar el borrador de trabajo actual del TS sobre cómo se ha is_detected y se pueden implementar amigos. Es bastante similar a can_apply en la respuesta de @ Yakk.

Usando el void_t Walter Brown :

 template  using void_t = void; 

Es muy fácil hacer ese tipo de rasgo:

 template struct has_to_string : std::false_type { }; template struct has_to_string()))> > : std::true_type { }; 

En primer lugar, creo que SFINAE generalmente debe estar oculto de las interfaces. Hace la interfaz desordenada. Ponga el SFINAE lejos de la superficie, y use despacho de tags para elegir una sobrecarga.

En segundo lugar, incluso escondo SFINAE de la clase de rasgos. Escribir en mi experiencia el código “puedo hacer X” es lo suficientemente común como para no tener que escribir código SFINAE desordenado para hacerlo. Entonces, en cambio, escribo un rasgo genérico can_apply , y tengo un rasgo que SFINAE falla si pasa los tipos incorrectos usando decltype .

Luego alimentamos el carácter de decltype fallido de decltype a can_apply , y obtenemos un tipo verdadero / falso dependiendo de si la aplicación falla.

Esto reduce el trabajo por rasgo “puedo hacer X” a una cantidad mínima, y ​​coloca el código SFINAE un tanto complicado y frágil fuera del trabajo diario.

Yo uso C ++ 1z void_t . Implementarlo usted mismo es fácil (al final de esta respuesta).

Se propone una metafunción similar a can_apply para la estandarización en C ++ 1z, pero no es tan estable como void_t , por lo que no la estoy usando.

En primer lugar, un espacio de nombre de details para ocultar la implementación de can_apply de ser encontrado por accidente:

 namespace details { template 

Luego podemos escribir can_apply en términos de details::can_apply , y tiene una interfaz más agradable (no requiere que se pase el void adicional):

 template 

Lo anterior es código de metaprogtwigción de ayudante genérico. Una vez que lo tenemos en su lugar, podemos escribir una clase de rasgos can_to_string muy limpiamente:

 template using to_string_t = decltype( std::to_string( std::declval() ) ); template using can_to_string = can_apply< to_string_t, T >; 

y tenemos un rasgo can_to_string que es verdadero si podemos to_string a T

El trabajo que se requiere para escribir un nuevo rasgo como ese ahora tiene de 2 a 4 líneas de código simple, simplemente haz un decltype using alias, y luego haz una prueba de can_apply sobre él.

Una vez que tenemos eso, utilizamos el envío de tags a la implementación adecuada:

 template std::string stringify(T t, std::true_type /*can to string*/){ return std::to_string(t); } template std::string stringify(T t, std::false_type /*cannot to string*/){ return static_cast(ostringstream() < < t).str(); } template std::string stringify(T t){ return stringify(t, can_to_string{}); } 

Todo el código feo se esconde en el espacio de nombres de details .

Si necesita un void_t , use esto:

 templatestruct voider{using type=void;}; templateusing void_t=typename voider::type; 

que funciona en la mayoría de los principales comstackdores de C ++ 11.

Tenga en cuenta que la templateusing void_t=void; más simple templateusing void_t=void; no funciona en algunos comstackdores anteriores de C ++ 11 (había una ambigüedad en el estándar).

Puedes escribir un rasgo de ayuda para esto usando la expresión SFINAE:

 namespace detail { //base case, to_string is invalid template  auto has_to_string_helper (...) //... to disambiguate call -> false_type; //true case, to_string valid for T template  auto has_to_string_helper (int) //int to disambiguate call -> decltype(std::to_string(std::declval()), true_type{}); } //alias to make it nice to use template  using has_to_string = decltype(detail::has_to_string_helper(0)); 

Luego use std::enable_if_t::value>

Manifestación

Creo que hay dos problemas: 1) Encontrar todos los algoritmos viables para un tipo dado. 2) Seleccione el mejor.

Podemos, por ejemplo, especificar manualmente un pedido para un conjunto de algoritmos sobrecargados:

 namespace detail { template std::string stringify(choice<0>, T&& t) { using std::to_string; return to_string(std::forward(t)); } template std::string stringify(choice<1>, char const(&arr)[N]) { return std::string(arr, N); } template std::string stringify(choice<2>, T&& t) { std::ostringstream o; o < < std::forward(t); return std::move(o).str(); } } 

El primer parámetro de función especifica el orden entre esos algoritmos (“primera opción”, “segunda opción”, …). Para seleccionar un algoritmo, simplemente enviamos a la mejor pareja viable:

 template auto stringify(T&& t) -> decltype( detail::stringify(choice<0>{}, std::forward(t)) ) { return detail::stringify(choice<0>{}, std::forward(t)); } 

¿Cómo se implementa esto? Robamos un poco de Xeo @ Flaming Dangerzone y Paul @ void_t “puede implementar conceptos”? (usando implementaciones simplificadas):

 constexpr static std::size_t choice_max = 10; template struct choice : choice { static_assert(N < choice_max, ""); }; template<> struct choice {}; #include  template struct models : std::false_type {}; template struct models()...), void())> : std::true_type {}; #define REQUIRES(...) std::enable_if_t::value>* = nullptr 

Las clases de elección heredan de peores elecciones: la choice<0> hereda de la choice<1> . Por lo tanto, para un argumento de tipo choice<0> , un parámetro de función de tipo choice<0> es una mejor coincidencia que choice<1> , que es una mejor coincidencia que choice<2> y así sucesivamente [over.ics.rank ] p4.4

Tenga en cuenta que el desempate más especializado se aplica solo si ninguna de las dos funciones es mejor. Debido al orden total de choice , nunca entraremos en esa situación. Esto evita que las llamadas sean ambiguas, incluso si son viables múltiples algoritmos.

Definimos nuestros rasgos de tipo:

 #include  #include  namespace helper { using std::to_string; struct has_to_string { template auto requires_(T&& t) -> decltype( to_string(std::forward(t)) ); }; struct has_output_operator { std::ostream& ostream(); template auto requires_(T&& t) -> decltype(ostream() < < std::forward(t)); }; } 

Las macros se pueden evitar usando una idea de R. Martinho Fernandes :

 template using requires = std::enable_if_t::value, int>; // exemplary application: template = 0> std::string stringify(choice<0>, T&& t) { using std::to_string; return to_string(std::forward(t)); } 

Bueno, puedes omitir toda la magia de metaprogtwigción y usar el adaptador fit::conditional de la biblioteca Fit :

 FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional( [](auto x) -> decltype(to_string(x)) { return to_string(x); }, [](auto x) -> decltype(static_cast(ostringstream() < < x).str()) { return static_cast(ostringstream() < < x).str(); } ); 

O incluso más compacto, si no te molestan las macros:

 FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional( [](auto x) FIT_RETURNS(to_string(x)), [](auto x) FIT_RETURNS(static_cast(ostringstream() < < x).str()) ); 

Tenga en cuenta que también restringí la segunda función, por lo tanto, si no se puede to_string el tipo con to_string ni se puede transmitir a ostringstream , no se puede ostringstream la función. Esto ayuda con mejores mensajes de error y una mejor capacidad de comstackción con requisitos de tipo de comprobación.