我正在尝试创建一个类型特征类,以确定是否可以通过T
的{{1}}运算符流式传输特定类型<<
。我正在使用简单的SFINAE技术。
最终,我试图评估替换失败的表达式是:
std::ostream
我的期望是,如果decltype(std::declval<std::ostream>() << std::declval<T>()) ;
类型的实例t
和T
实例std::ostream
,如果表达式os
格式不正确,应该发生替换失败。
但显然,无论类型os << t
如何,替换失败都不会发生在这里。即使我只是在SFINAE的上下文之外使用上面的T
表达式声明typedef
,即使decltype
无法与T
一起使用,它也会很乐意编译。< / p>
例如:
std::ostream
上面的编译将使用GCC 4.9.2编译,这不是我的预期,因为struct Foo { };
int main()
{
// This compiles fine using GCC 4.9.2
//
typedef decltype(
std::declval<std::ostream>() << std::declval<Foo>()
) foo_type;
}
运算符没有重载以使用类型<<
。当然,如果我说:
Foo
...我收到编译错误。那么为什么上面的std::cout << Foo();
表达式甚至可以编译呢?
答案 0 :(得分:16)
C ++ 11添加了以下operator<<
重载:
template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
const T& value );
这转发到标准插入运算符,它不能将对std::ostream
的rvalue引用绑定,因为它们采用非const引用。由于std::declval<std::ostream>
返回std::ostream&&
,因此选择此重载,然后由于非常宽松的接口(即,如果没有有效的底层插入运算符,则不是SFINAEd),您的{{1} }说明符有效。
简单的解决方法是使用decltype
。这将返回std::declval<std::ostream&>()
,因此std::ostream&
说明符不会选择模板重载,并且编译需要正常的插入运算符重载:
decltype
Clang输出:
typedef decltype(
std::declval<std::ostream&>() << std::declval<Foo>()
) foo_type;
这是一个更简单的例子,它表现出同样的问题:
main.cpp:8:39: error: invalid operands to binary expression ('std::basic_ostream<char>' and 'Foo')
std::declval<std::ostream&>() << std::declval<Foo>()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~
以下是相关标准报价(N4140):
必须实例化声明,因为涉及重载解析:
#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; }
如果以涉及重载的方式使用函数模板或成员函数模板特化 决议,专门化的声明被隐含地实例化(14.8.3)。
只需要实例化声明:
[temp.inst]/10:
只需要一个函数模板特化的签名即可在一组中输入特化 候选职能。因此,只需要函数模板声明来解析对其的调用 模板专业化是候选人。
并且不允许实现实例化函数体:
[temp.over]/5:
实现不得隐式实例化函数模板,变量模板,成员模板,非虚拟成员函数,成员类或不需要的类模板的静态数据成员实例
答案 1 :(得分:2)
我们是否真的回答了为什么会发生这种情况,但如果您使用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 {};
似乎有效。
答案 2 :(得分:1)
如果您查看标题文件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
)。