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

时间:2014-03-31 09:39:46

标签: c++ templates c++11 iostream template-meta-programming

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

我错过了一些东西,因为我总是认为一个简单的空类没有操作符。

这里是代码:

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

我做错了什么?

6 个答案:

答案 0 :(得分:38)

显然operator<<的这个超载会踩到你的方式并使traling返回类型中的表达式有效:

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

this reference page上的(3)。它是一个简单的转发器(调用os << value),它是在C ++ 11中添加的,允许插入rvalue-streams,因为它们不会绑定到带左值引用的重载。

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

如果你明确要求左值参考,可以回避这个问题:std::declval<SS&>()

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

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

答案 1 :(得分:4)

我不完全确定问题是什么,但是如果您删除了std::forward,它就有效,而且我认为无论如何它们都不是必需的:

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

Live example

答案 2 :(得分:4)

当值传递给需要左值的函数(即TheThruth(const bool& t))时,

jrok's answer会导致链接错误。所以现在在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建议名称:operator >>(欢迎使用更好的名称建议)。

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

答案 3 :(得分:3)

“灵感来自”ypw的答案(ypw - 如果你相应地编辑你的 - 或者创建一个新的来消除掉票 - 我将删除它并提升你的):

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

讨论

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

答案 4 :(得分:1)

编辑:由@jrok发现,它存在一个通用运算符&lt;&lt;对于非常相互影响的右值流。

这里有些问题,如果你看一下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

答案 5 :(得分:0)

并测试“ >>”运算符:

template<typename I, typename T> class is_istreamable
{
    template<typename II, typename TT>
    static auto test (int) -> decltype (std::declval<II&>() >> std::declval<TT&>(), std::true_type ());

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

public:

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