SFINAE会员通过
SFINAE Member Pass through
我想知道是否有可能创建一个类,作为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的精细艺术也不是很精通。也许有人可以改进/修理它?因为我很迷茫
我通常更喜欢创建自定义的enable_if
风格类型,因为我发现使用单个trait类型比使用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
绑定到非const的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
不是很好,但基本上它接受指向数据成员类型的指针,并给出成员的类型)
虽然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。
- 为什么使用SFINAE而不是函数重载
- 如何使用模板函数的函数签名进行SFINAE
- 数据成员SFINAE的C++17测试:gcc vs clang
- 使用在用于SFINAE的void_t中具有参数的方法
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 提供与TMP和SFINAE的通用接口
- "Inverse SFINAE"避免模棱两可的过载
- 表达式 SFINAE:如何根据类型是否包含具有一个或多个参数的函数来选择模板版本
- 如何在儿童类中使用SFINAE
- 使用 SFINAE 作为模板参数的编译时递归
- 使用 SFINAE 设计模板方法
- 与SFINAE支票交朋友
- C++许多 SFINAE 风格的过载
- 是否可以混合使用SFINAE和模板专业化?
- C++表达SFINAE和ostream操纵器
- SFINAE不能防止模棱两可的操作员过载吗?
- SFINAE是否取决于类型推断?
- MSVC 无法编译 SFINAE 检查
- SFINAE 检查模板参数运算符
- SFINAE 与 numeric_limits<T>::max() 在 MSVC2017 上