c++ templates c++11 iostream template-meta-programming

c++ - ¿Cómo puedo detectar si un tipo puede transmitirse a std:: ostream?



templates c++11 (5)

"Inspirado" por la respuesta de ypw (ypw, si editas el tuyo según corresponda) o crea uno nuevo para deshacerte de los votos negativos, lo eliminaré y votaré por tu cuenta):

template <typename T> class is_streamable { template <typename U> // must be template to get SFINAE fall-through... static auto test(const U* u) -> decltype(std::cout << *u); static auto test(...) -> std::false_type; public: enum { value = !std::is_same<decltype(test((T*)0)), std::false_type>::value }; };

Discusión

El punto principal de esta respuesta es resaltar cuán inútil es toda la preocupación acerca de las referencias de rvalue / declvar , declvar , forward etc. para este problema. Recuerde que solo estamos haciendo una afirmación en tiempo de compilación de que la notación de transmisión es compatible: no hay tiempo de ejecución para las consideraciones de eficiencia de tiempo de ejecución, como los tipos de referencias a la materia, ni existe la necesidad de usar declvar para crear una transmisión como si no estaba disponible Este código lo mantiene simple y creo que tiene una utilidad total, la evidencia es lo contrario bienvenido.

Estoy intentando escribir un rasgo de tipo para detectar si un tipo ha sobrecargado el operador << () adecuado para usar en un flujo de salida.

Me falta algo porque siempre me estoy volviendo cierto para una clase vacía simple sin operadores en absoluto.

Aquí el código:

template<typename S, typename T> class is_streamable { template<typename SS, typename TT> static auto test(SS&& s, TT&& t) -> decltype(std::forward<SS>(s) << std::forward<TT>(t)); struct dummy_t {}; static dummy_t test(...); using return_type = decltype(test(std::declval<S>(), std::declval<T>())); public: static const bool value = !std::is_same<return_type, dummy_t>::value; }; class C {}; int main() { std::cout << is_streamable<std::stringstream, C>::value << std::endl; return 0; }

Salida:

1

Aquí está en ideone: https://ideone.com/ikSBoT

¿Qué estoy haciendo mal?


Aparentemente, esta sobrecarga de operator<< es un paso en tu camino y hace que la expresión en el tipo de retorno de trailing sea válida:

template< class CharT, class Traits, class T > basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os, const T& value );

Ver (3) en esta página de referencia . Es un reenviador simple (que llama a os << value ) que se agregó en C ++ 11 para permitir la inserción a rvalue-streams porque no se vinculan a sobrecargas que toman una referencia lvalue.

Entonces, el problema es que std::declval<SS>() devuelve una referencia rvalue y esta sobrecarga entra en juego. La llamada está bien formada, pero debido a que la función no se instancia, no se obtiene un error, incluso si el valor no es transmisible.

Esto se puede eludir si solicita explícitamente la referencia std::declval<SS&>() : std::declval<SS&>() .

También sugeriría una implementación ligeramente diferente, sin pasar el flujo y el valor para test . Puedes usar declval directamente dentro de decltype . Junto con el operador de coma, se ve así:

#include <type_traits> #include <utility> #include <iostream> #include <sstream> template<typename S, typename T> class is_streamable { template<typename SS, typename TT> static auto test(int) -> decltype( std::declval<SS&>() << std::declval<TT>(), std::true_type() ); template<typename, typename> static auto test(...) -> std::false_type; public: static const bool value = decltype(test<S,T>(0))::value; }; class C {}; int main() { std::cout << is_streamable<std::stringstream, C>::value << std::endl; return 0; }


EDITAR: Tal como lo encontró @jrok, existe un operador genérico << para flujos rvalue que interactúan mal.

Algo está realmente mal aquí, si nos fijamos en el código de abajo probado en coliru , las 2 últimas líneas se compilan incluso si no deberían ...

std::stringstream ss; B b; int v; std::cout << typeid(decltype(ss>>v )).name() << "/n" ; std::cout << typeid(decltype(ss<<1 )).name() << "/n" ; std::cout << typeid(decltype(std::declval<std::stringstream>()>>v )).name() << "/n" ; std::cout << typeid(decltype(std::declval<std::stringstream>()<<1 )).name() << "/n" ; //std::cout << typeid(decltype(ss>>b )).name() << "/n" ; // do not compile //std::cout << typeid(decltype(ss<<b )).name() << "/n" ; // do not compile std::cout << typeid(decltype(std::declval<std::stringstream>()>>b )).name() << "/n" ; // should not compile but succeed std::cout << typeid(decltype(std::declval<std::stringstream>()<<b )).name() << "/n" ; // should not compile but succeed


No estoy del todo seguro de cuál es el problema, pero funciona si elimina los std::forward s, y no creo que sean necesarios aquí de todos modos:

template<typename SS, typename TT> static auto test(SS&& s, TT&& t) -> decltype(s << t);

Ejemplo en vivo


share provoca errores de vinculación cuando el valor pasa a una función que requiere un lvalue (es decir, TheThruth(const bool& t) ). Entonces ahora en C ++ 17 tenemos la plantilla void_t . Y basado en el ejemplo de CPPReference, escribí y probé lo siguiente:

#include <iostream> #include <typeinfo> template<typename S, typename T, typename = void> struct is_to_stream_writable: std::false_type {}; template<typename S, typename T> struct is_to_stream_writable<S, T, std::void_t< decltype( std::declval<S&>()<<std::declval<T>() ) > > : std::true_type {}; class Foo { public: Foo(){} }; void TheTruth(const bool& t) { std::cout<< t<< std::endl; } int main() { std::cout<< is_to_stream_writable<std::ostream,int>::value <<std::endl; std::cout<< is_to_stream_writable<std::ostream,Foo>::value <<std::endl; TheTruth( is_to_stream_writable<std::ostream,int>::value ); }

También tenga en cuenta que el nombre is_to_stream_writable ajusta mejor al operator << y sugiere el nombre: is_from_stream_readable para el operator >> (se is_from_stream_readable sugerencias de mejor nombre).

El código compila con g++ -std=c++1z -O0 -Wall -pedantic main.cpp , gcc versiones 6.2 y 7.2 y en Coliru .