繁体   English   中英

使用std :: ostream的declval表达式(对于SFINAE)

[英]declval expression (for SFINAE) with std::ostream

我正在尝试创建一个类型traits类来确定是否可以通过std::ostream<<运算符流式传输特定类型T 我正在使用简单的SFINAE技术。

最终,我试图评估替换失败的表达式是:

decltype(std::declval<std::ostream>() << std::declval<T>()) ;

我的期望是,给定一个类型为T的实例t和一个std::ostream实例os ,如果表达式os << t os ,则应该发生替换失败。

但显然替代失败从未发生在这里,无论T型。 即使我只是使用上面的decltype表达式声明一个typedef ,在SFINAE的上下文之外,它也很乐意编译,即使T不能与std::ostream一起使用。

例如:

struct Foo  { };

int main()
{
    // This compiles fine using GCC 4.9.2
    //
    typedef decltype(
        std::declval<std::ostream>() << std::declval<Foo>()
    ) foo_type;
}

以上将使用GCC 4.9.2进行编译,这不是我预期的,因为<<运算符没有重载以使用类型Foo 当然,如果我说:

std::cout << Foo();

...我收到编译器错误。 那么为什么上面的decltype表达式甚至可以编译呢?

C ++ 11添加了以下operator<< overload:

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

这转发到标准插入运算符,它不能将rvalue引用绑定到std::ostream s,因为它们采用非const引用。 由于std::declval<std::ostream>返回std::ostream&& ,所以选择了这个重载,那么由于非常宽松的接口(即如果没有有效的底层插入操作符,这不是SFINAEd),你的decltype说明符作品。

简单的解决方法是使用std::declval<std::ostream&>() 这将返回一个std::ostream& ,因此你的decltype说明符不会选择模板重载,并且编译时需要正常的插入操作符重载:

typedef decltype(
    std::declval<std::ostream&>() << std::declval<Foo>()
) foo_type;

Clang输出:

main.cpp:8:39: error: invalid operands to binary expression ('std::basic_ostream<char>' and 'Foo')
        std::declval<std::ostream&>() << std::declval<Foo>()
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^  ~~~~~~~~~~~~~~~~~~~

现场演示


这是一个更简单的例子,它表现出同样的问题:

#include <string>

void foo (int&,int){}
void foo (int&,float){}

template <typename T>
void foo (int&& a, T b) {
    foo(a, b);
}

int main()
{
    std::string s;
    typedef decltype(foo(1,s)) foo_type;
}

现场演示


以下是相关标准报价(N4140):

必须实例化声明,因为涉及重载解析:

[temp.inst]/10:如果以涉及重载解析的方式使用函数模板或成员函数模板特化,则隐式实例化特化的声明(14.8.3)。

只需要声明声明:

[temp.over]/5:只需要函数模板特化的签名即可在一组候选函数中输入特化。 因此,仅需要函数模板声明来解析模板特化是候选的调用。

并且不允许实现实例化函数体:

[temp.inst]/11:实现不得隐式实例化函数模板,变量模板,成员模板,非虚拟成员函数,成员类或不需要的类模板的静态数据成员实例。

没有真正回答为什么会发生这种情况,但如果你用std::stream&替换如下:

template<typename T, typename Enable = std::ostream&>
struct can_be_streamed : std::false_type {};
template<typename T>
struct can_be_streamed<T, 
         decltype(std::declval<std::ostream&>() << std::declval<T>())> : std::true_type {};

似乎工作。

现场演示

如果你查看头文件ostream ,你会发现因为std::declval生成rvlaue引用,实际上有一个匹配的泛型operator<<

#if __cplusplus >= 201103L
  /** 
   *  @brief  Generic inserter for rvalue stream
   *  @param  __os  An input stream.
   *  @param  __x  A reference to the object being inserted.
   *  @return  os  
   *   
   *  This is just a forwarding function to allow insertion to
   *  rvalue streams since they won't bind to the inserter functions
   *  that take an lvalue reference.
  */  
  template<typename _CharT, typename _Traits, typename _Tp>
    inline basic_ostream<_CharT, _Traits>&
    operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
    {   
      __os << __x;
      return __os;
    }   
#endif // C++11

这就解释了为什么你没有失败的原因。 但是,这实际上无法与std::cout << Foo()调用匹配。 以下是编译错误的相关部分:

/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/6.1.0/../../../../include/c++/6.1.0/ostream:628:5: note: candidate function [with _CharT = char, _Traits = std::char_traits<char>, _Tp = Foo] not viable: no known conversion from 'ostream' (aka 'basic_ostream<char>') to 'basic_ostream<char, std::char_traits<char> > &&' for 1st argument
    operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
    ^

这里的问题是lhs只能是一个右值引用,但你(显然)在调用中使用了左值(即std::cout )。

暂无
暂无

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

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