我想知道是否有可能创建一个类,作为std::enable_if和SFINAE成员检测器之间的组合。
class foo
{
public:
int bar;
};
template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
return value.bar;
}所以我试着这么做。
class foo
{
public:
int bar;
};
template <class C, C>
class Check;
template <class T, class Enable = void>
class enable_if_has_bar
{};
template <class T>
class enable_if_has_bar<T, Check <decltype(&T::bar),&T::bar>>
{
public:
typedef decltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0)) type;
};
template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
return value.bar;
}
int main ()
{
foo foobar;
foobar.bar = 42;
cout << ReturnBar(foobar) << endl;
}( http://ideone.com/WKTfmQ )
它似乎不起作用,而且我对SFINAE的艺术也不是很精通。也许有人可以改进/修复它?因为我不知所措。
发布于 2012-12-31 08:13:32
我通常更喜欢像您所尝试的那样创建定制的enable_if-style类型,因为我发现使用单个特征类型而不是enable_if<some_trait<T>, another_trait<T>>的组合可以更清晰地阅读代码。但在这种情况下,您的代码中存在一些问题,使其无法正常工作。
您的enable_if_has_bar专门化将永远不会被选中,ReturnBar的返回类型只实例化主模板enable_if_has_bar<foo, void>,并且从不定义嵌套的type。没有任何东西会导致专门化被实例化,因此不会有任何东西来检查T::bar是否是有效的表达式。
您的decltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0))表达式将生成int&,而不是您想要的int。这是因为decltype(foobar.*(&foo::bar))等同于decltype(foobar.bar),而foobar.bar是一个左值,所以decltype是int&。如果函数ReturnBar返回int&,那么它将无法编译,因为参数value是const,所以您不能将value.bar绑定到非常数int&。
下面是一个工作版本:
template <class T>
class has_bar
{
template<typename U, typename = decltype(&U::bar)>
static std::true_type
test(U*);
static std::false_type
test(...);
public:
static const int value = decltype(test((T*)nullptr))::value;
};
template<typename T, bool = has_bar<T>::value>
struct enable_if_has_bar
{ };
template<typename T>
struct enable_if_has_bar<T, true>
: std::decay<decltype(std::declval<T&>().*(&T::bar))>
{ };这首先声明帮助器has_bar来回答该类型是否具有嵌套成员的问题。该帮助器使用SFINAE来获取true或false值。如果&T::bar是一个有效的表达式,那么将使用test的第一个重载,它返回true_type,因此value将被设置为true_type::value,即true。否则,将选择回退过载,并将value设置为false。
然后,enable_if_has_bar模板使用默认模板参数,该参数被推导为has_bar<T>::value的值。当has_bar<T>::value为false时,将使用主模板。当has_bar<T>::value为true时使用特殊化,在这种情况下,我们知道表达式&T::bar是有效的,并且可以在decltype表达式中使用它来获取类型。
std::decay用于将decltype表达式的int&结果转换为int。从decay继承比使用它来定义成员要短一些,这将是:
typedef typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type type;在未计算的表达式中,我使用了标准实用程序declval<T>(),它比static_cast<T*>(0)输入更短,也更有表达能力。
除了使用decay之外,还可以使用另一个帮助器类型从类型int T::*中获取类型int,例如
template<typename T>
struct remove_class;
{ };
template<typename Member, typename Class>
struct remove_class<Member Class::*>
{
typedef Member type;
};
template<typename T>
struct enable_if_has_bar<T, true>
: remove_class<decltype(&T::bar)>
{ };(名称remove_class不是很好,但基本上它接受一个指向数据成员的指针类型,并给出成员的类型。)
发布于 2014-08-11 19:22:39
尽管Jonathan Wakely的答案非常好,但让我提供一个稍微不同的模式:
//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;
//here is the use case
template<typename T, typename = void>
struct enable_if_has_bar{ };
template<typename T>
struct enable_if_has_bar<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>>
: std::decay<decltype(std::declval<T&>().*(&T::bar))>
{ };这里有一个活生生的例子:http://ideone.com/un0ZgH
尽管这自然是一个品味问题,但我喜欢使用类型接收器模式,因为SFINAE测试的语法与我在元函数主体中实际使用的语法完全相同,并且彼此相邻。因此,希望它不容易出现bug。
https://stackoverflow.com/questions/14095872
复制相似问题