我正在尝试编写一个类型特征来检测一个类型是否重载了适合用于输出流的operator <<()。
我错过了一些东西,因为我总是认为一个简单的空类没有操作符。
这里的代码:
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 );
关于this reference page,请参阅(3)。它是一个简单的转发器(调用os << value
),它是在C ++ 11中添加的,允许插入rvalue-streams,因为它们不会绑定到带左值引用的重载。
所以,问题是std::declval<SS>()
返回一个右值引用并且这个重载开始。调用本身是格式良好的,但是因为函数本身没有实例化,所以即使值不可流化也不会出错。
如果你明确要求左值参考:std::declval<SS&>()
,这可以回避。
我还建议稍微不同的实现,而不将流和值传递给test
。你可以直接在declval
里面使用decltype
。与逗号运算符一起,它看起来像这样:
#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;
}
我不完全确定问题是什么,但是如果你删除std::forward
s它会起作用,我认为无论如何它们都不是必需的:
template<typename SS, typename TT>
static auto test(SS&& s, TT&& t) -> decltype(s << t);
由ypw的回答“启发”(ypw - 如果你相应地编辑你的 - 或者创建一个新版本来摆脱downvotes - 我将删除它并向你投票):
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<decltype(test((T*)0)), std::false_type>::value };
};
这个答案的要点是强调对于这个问题,所有对rvalue / lvalue引用,declvar
,forward
ing等的担心毫无意义。请记住,我们只是在编译时断言支持流表示法 - 运行时效率考虑因素没有运行时间,例如对事物的引用类型,也不需要使用declvar
来创建流,就像在那里一样没有。这段代码保持简单,我相信它具有完整的实用性 - 相反的证据最受欢迎。
当值传递给需要左值的函数(即jrok's answer)时,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进行编译。
编辑:正如@jrok所发现的那样,它存在一个通用运算符<< for rvalue streams,它们之间的交互很糟糕。
这里有些东西真的是错的,如果你看看下面的代码在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