繁体   English   中英

如何检测类型是否可以流式传输到 std::ostream?

[英]How can I detect if a type can be streamed to an std::ostream?

我正在尝试编写一个类型特征来检测类型是否重载了适合用于输出流的 operator<<() 。

我错过了一些东西,因为对于一个根本没有运算符的简单空类,我总是很真实。

这里的代码:

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;
}

输出:

1

这是在ideone: https ://ideone.com/ikSBoT

我究竟做错了什么?

显然, operator<<这种重载阻碍了您的工作,并使 traling 返回类型中的表达式有效:

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

请参阅此参考页上的 (3)。 这是一个简单的转发器(调用os << value ),在 C++11 中添加以允许插入到右值流,因为它们不绑定到采用左值引用的重载。

所以,问题是std::declval<SS>()返回一个右值引用,这个重载开始了。调用本身是格式良好的,但是因为函数本身没有被实例化,你甚至不会得到错误如果值不可流式传输。

如果您明确要求左值引用,则可以回避这一点: std::declval<SS&>()

我还建议稍微不同的实现,而不将流和值传递给test 您可以直接在decltype使用declval 加上逗号运算符,它看起来像这样:

#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;
}

当值传递给需要左值的函数(即TheThruth(const bool& t) )时, jrok 的回答会导致链接错误。 所以现在在 C++17 中我们有模板void_t 基于 CPPReference 上的示例,我编写并测试了以下内容:

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

}

另请注意,名称is_to_stream_writable更适合operator <<并建议名称: is_from_stream_readable for operator >> (欢迎提供更好的名称建议)。

代码使用g++ -std=c++1z -O0 -Wall -pedantic main.cpp 、gcc 版本 6.2 和 7.2 以及Coliru 编译

我不完全确定问题是什么,但如果你删除std::forward s,它会起作用,而且我认为它们在这里没有必要:

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

活生生的例子

一种简单的方法...

template <typename T, class = void>
struct is_streamable : std::false_type { };

template <typename T>
struct is_streamable<T, std::void_t<decltype(std::cout << *(T*)0)>>
  : std::true_type { };

或者,受到 ypw 的回答的“启发”(ypw - 如果你相应地编辑你的 - 或者创建一个新的来摆脱downvotes - 我会删除这个并upvote你的):

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_v<decltype(test((T*)0)), std::false_type> };
};

讨论

这个答案的要点是强调所有关于右值/左值引用、 declvarforward ing 等的担忧对于这个问题是多么毫无意义。 请记住,我们只是在执行流表示法支持的编译时断言 - 没有运行时考虑运行时效率问题,例如对事物的引用类型,也不需要使用declvar来创建流,好像有没有可用的。 这段代码保持简单,我相信它具有完整的实用性 - 最欢迎相反的证据。

编辑:正如@jrok 所发现的,对于交互不良的右值流,它存在一个通用运算符<<。

这里确实有问题,如果您查看下面在coliru 上测试的代码,最后两行即使它们不应该编译...

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

并测试“>>”运算符:

template<typename IOS, typename T> class can_read_from_ios
{
   static_assert (std::is_base_of<std::ios, IOS>::value);

   template<typename ios, typename t> \
   static auto test (int) -> decltype (std::declval<ios&> () >> std::declval<t&> (), std::true_type ());

   template<typename, typename> static auto test (...) -> std::false_type;

public:

   static const bool value = decltype (test<IOS, T> (0))::value;
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM