std::ostream 的 declval 表达式(对于 SFINAE)
declval expression (for SFINAE) with std::ostream
我正在尝试创建类型特征 class 以确定特定类型 T
是否可以通过 std::ostream
的 <<
运算符进行流式传输。我正在使用一种简单的 SFINAE 技术。
最终,我尝试评估替换失败的表达式是:
decltype(std::declval<std::ostream>() << std::declval<T>()) ;
我的期望是,给定一个 T
类型的实例 t
和一个 std::ostream
实例 os
,如果表达式 os << t
是错误的-形成,应该发生替换失败。
但显然无论类型如何,这里都不会发生替换失败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<<
重载:
template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
const T& value );
这会转到标准插入运算符,它不能将右值引用绑定到 std::ostream
s,因为它们采用非常量引用。由于 std::declval<std::ostream>
returns std::ostream&&
,选择了这个重载,然后由于非常宽松的接口(即如果没有有效的底层插入运算符,这不是 SFINAEd),你的 decltype
说明符有效。
简单的修复方法是使用 std::declval<std::ostream&>()
。这将 return 一个 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:
If a function template or a member function template specialization is used in a way that involves overload
resolution, a declaration of the specialization is implicitly instantiated (14.8.3).
只需要实例化声明:
[temp.over]/5:
Only the signature of a function template specialization is needed to enter the specialization in a set of
candidate functions. Therefore only the function template declaration is needed to resolve a call for which
a template specialization is a candidate.
且不允许实例化函数体的实现:
[temp.inst]/11:
An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, or a static data member of a class template that does not require instantiation.
如果你看头文件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
)。
并没有真正回答为什么会发生这种情况,但如果您将其替换为 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 {};
似乎有效。
我正在尝试创建类型特征 class 以确定特定类型 T
是否可以通过 std::ostream
的 <<
运算符进行流式传输。我正在使用一种简单的 SFINAE 技术。
最终,我尝试评估替换失败的表达式是:
decltype(std::declval<std::ostream>() << std::declval<T>()) ;
我的期望是,给定一个 T
类型的实例 t
和一个 std::ostream
实例 os
,如果表达式 os << t
是错误的-形成,应该发生替换失败。
但显然无论类型如何,这里都不会发生替换失败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<<
重载:
template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
const T& value );
这会转到标准插入运算符,它不能将右值引用绑定到 std::ostream
s,因为它们采用非常量引用。由于 std::declval<std::ostream>
returns std::ostream&&
,选择了这个重载,然后由于非常宽松的接口(即如果没有有效的底层插入运算符,这不是 SFINAEd),你的 decltype
说明符有效。
简单的修复方法是使用 std::declval<std::ostream&>()
。这将 return 一个 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:
If a function template or a member function template specialization is used in a way that involves overload resolution, a declaration of the specialization is implicitly instantiated (14.8.3).
只需要实例化声明:
[temp.over]/5:
Only the signature of a function template specialization is needed to enter the specialization in a set of candidate functions. Therefore only the function template declaration is needed to resolve a call for which a template specialization is a candidate.
且不允许实例化函数体的实现:
[temp.inst]/11:
An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, or a static data member of a class template that does not require instantiation.
如果你看头文件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
)。
并没有真正回答为什么会发生这种情况,但如果您将其替换为 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 {};
似乎有效。