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);
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 .