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

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

本文关键字:传输 std ostream 何检测 检测 是否 类型      更新时间:2023-10-16

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

我错过了一些东西,因为我总是对一个根本没有运算符的简单空类感到满意。

这里的代码:

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)。它是在 C++11 中添加的一个简单的转发器(调用 os << value ),以允许插入到重值流,因为它们不会绑定到采用左值引用的重载。

因此,问题是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;
}

> 当值传递给需要左值的函数时,jrok 的答案会导致链接错误(即 TheThruth(const bool& t))。所以现在在 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 进行编译。

我不

完全确定问题是什么,但是如果您删除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 - 如果您相应地编辑您的 - 或创建一个新的来摆脱反对票 - 我会删除它并投赞成票):

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等问题的担忧都是毫无意义的。 请记住,我们只是在做一个支持流式处理符号的编译时断言 - 运行时没有运行时考虑效率,例如对物质的引用类型,也不需要使用 declvar 来创建流,就好像没有可用的一样。 这段代码保持简单,我相信它有充分的实用性 - 相反的证据最受欢迎。

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

这里真的出了点问题,如果你看看下面在 coliru 上测试的代码,最后 2 行编译即使它们不应该......

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