【问题标题】:How can I detect if a type can be streamed to an std::ostream?如何检测类型是否可以流式传输到 std::ostream?
【发布时间】:2014-05-10 14:21:32
【问题描述】:

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

我遗漏了一些东西,因为对于一个根本没有运算符的简单空类,我总是如此。

代码如下:

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

我做错了什么?

【问题讨论】:

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


    【解决方案1】:

    显然,operator&lt;&lt; 的这种重载阻碍了您的使用,并使跟踪返回类型中的表达式有效:

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

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

    所以,问题在于std::declval&lt;SS&gt;() 返回一个右值引用并且这个重载开始了。调用本身是格式良好的,但是由于函数本身没有被实例化,即使值是不可流式传输。

    如果您明确要求左值引用,则可以回避:std::declval&lt;SS&amp;&gt;()

    我还建议一个稍微不同的实现,不将流和值传递给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;
    }
    

    【讨论】:

    • @MatthieuM。你并不孤单!
    • 很好,我找不到合适的重载,我的答案是右值。
    • 谢谢!但这是否意味着,这可能会被报告为标准的缺陷?也许正确的方法应该是仅当流
    • @gigabytes 也许吧。我会说,I/O 库在制作时并未考虑到元编程。
    • 我非常喜欢这种基于 decltype 和 declval 的 SFINAE 风格。极具可读性和模板友好。干得好
    【解决方案2】:

    jrok's answer 在将值传递给需要左值的函数(即TheThruth(const bool&amp; 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 &lt;&lt; 并建议名称:is_from_stream_readable for operator &gt;&gt;(欢迎提供更好的名称建议)。

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

    【讨论】:

    • 这里有趣的一点是,在调用它之前一定不要忘记包含正确的标题。也就是说,is_to_stream_writable&lt;std::stringstream, int&gt;::value 将是 false 如果只包括 &lt;iosfwd&gt; (这使它编译),但如果包括 &lt;sstream&gt; 则返回 true。
    【解决方案3】:

    我不完全确定问题出在哪里,但如果您删除 std::forwards,它会起作用,而且我认为它们在这里没有必要:

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

    Live example

    【讨论】:

      【解决方案4】:

      一个简单的方法...

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

      讨论

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

      【讨论】:

      • 谁说sizeof(std::cout)不等于sizeof(char)
      • @ildjarn:使用了替代方案,尽管这个问题与恕我直言没有实际相关性
      • 相反,为一种可移植的、与实现无关的语言指出不可移植的、特定于实现的代码有很多相关性。 ;-]
      【解决方案5】:

      编辑:正如@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
      

      【讨论】:

        【解决方案6】:

        并测试“>>”运算符:

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

        【讨论】:

        • 欢迎来到 StackOverflow!您能否解释一下您的解决方案,以便让社区更清楚地了解?
        • 您好,感谢您的欢迎信息,很抱歉我的回复晚了。我已经更新了之前的代码作为答案。
        猜你喜欢
        • 1970-01-01
        • 2017-06-19
        • 1970-01-01
        • 1970-01-01
        • 2022-08-04
        • 1970-01-01
        • 2017-01-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多