C ++ SFINAE的例子?
我想进入更多的模板元编程。 我知道SFINAE代表“替代失败不是一个错误”。 但有人可以给我看SFINAE的好用吗?
下面是一个例子( 从这里 ):
template<typename T> class IsClassT { private: typedef char One; typedef struct { char a[2]; } Two; template<typename C> static One test(int C::*); // Will be chosen if T is anything except a class. template<typename C> static Two test(...); public: enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 }; enum { No = !Yes }; };
当IsClassT<int>::Yes
被评估时,0不能被转换为int int::*
因为int不是一个类,所以它不能有成员指针。 如果SFINAE不存在,那么你会得到一个编译器错误,像'0不能转换为非类typesint的成员指针'。 相反,它只是使用返回Two的...
表单,因此计算结果为false,int不是类types。
我喜欢用SFINAE
来检查布尔条件。
template<int I> void div(char(*)[I % 2 == 0] = 0) { /* this is taken when I is even */ } template<int I> void div(char(*)[I % 2 == 1] = 0) { /* this is taken when I is odd */ }
这可能相当有用。 例如,我用它来检查使用操作符逗号收集的初始化程序列表是否不大于固定大小
template<int N> struct Vector { template<int M> Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ } }
该列表只有在M小于N时才被接受,这意味着初始化列表的元素不是太多。
char(*)[C]
语法的意思是:指向元素types为char和大小为C
的数组。 如果C
为false(这里是0),那么我们得到无效typeschar(*)[0]
,指向一个零大小的数组:SFINAE使得模板将被忽略。
用boost::enable_if
表示,看起来像这样
template<int N> struct Vector { template<int M> Vector(MyInitList<M> const& i, typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ } }
在实践中,我经常会发现检查条件的能力有用的能力。
Boost的enable_if库为使用SFINAE提供了一个很好的干净的界面。 我最喜欢的用法示例之一是在Boost.Iterator库中。 SFINAE用于启用迭代器types转换。
在C ++ 11中,SFINAEtesting变得更漂亮了。 以下是一些常用的例子:
根据特性挑选一个函数超载
template<typename T> std::enable_if_t<std::is_integral<T>::value> f(T t){ //integral version } template<typename T> std::enable_if_t<std::is_floating_point<T>::value> f(T t){ //floating point version }
使用一个所谓的types汇成语,你可以做一个types检查是否有一个成员,如果该成员是一定的types
//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; //use case template<typename T, typename=void> struct HasBarOfTypeInt : std::false_type{}; template<typename T> struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> : std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{}; struct S{ int bar; }; struct K{ }; template<typename T, typename = TypeSinkT<decltype(&T::bar)>> void print(T){ std::cout << "has bar" << std::endl; } void print(...){ std::cout << "no bar" << std::endl; } int main(){ print(S{}); print(K{}); std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl; }
这里是一个现实的例子: http : //ideone.com/dHhyHE我最近也写了一个关于SFINAE和标签发送在我的博客(无耻的插件,但相关)整个部分http://metaporky.blogspot.de/2014/08/部分7-静态调度,function.html
注意从C ++ 14开始,有一个std :: void_t,它和我的TypeSink在本质上是一样的。
下面是另一个(晚) SFINAE的例子,基于格雷格罗杰斯的答案 :
template<typename T> class IsClassT { template<typename C> static bool test(int C::*) {return true;} template<typename C> static bool test(...) {return false;} public: static bool value; }; template<typename T> bool IsClassT<T>::value=IsClassT<T>::test<T>(0);
通过这种方式,你可以检查value
的值来看T
是否是一个类:
int main(void) { std::cout << IsClassT<std::string>::value << std::endl; // true std::cout << IsClassT<int>::value << std::endl; // false return 0; }
C ++ 17可能会提供一个通用的方法来查询function。 有关详细信息,请参阅N4502 ,但作为一个独立的示例,请考虑以下内容。
这部分是不变的部分,把它放在一个标题。
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf. template <typename...> using void_t = void; // Primary template handles all types not supporting the operation. template <typename, template <typename> class, typename = void_t<>> struct detect : std::false_type {}; // Specialization recognizes/validates only types supporting the archetype. template <typename T, template <typename> class Op> struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
下面的例子取自N4502 ,显示了用法:
// Archetypal expression for assignment operation. template <typename T> using assign_t = decltype(std::declval<T&>() = std::declval<T const &>()) // Trait corresponding to that archetype. template <typename T> using is_assignable = detect<T, assign_t>;
与其他的实现相比,这个工具非常简单:一套简化的工具( void_t
和detect
)就足够了。 此外,据报道(参见N4502 ),与以前的方法相比,它显着地更有效(编译时和编译器内存消耗)。
这是一个现场示例 ,其中包括GCC 5.1之前的可移植性调整。
下面是SFINAE的一篇很好的文章: C ++的SFINAE概念的介绍:编译时对类成员的反省 。
总结如下:
/* The compiler will try this overload since it's less generic than the variadic. T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr); int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors. It simply tries the next overload. */ template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { } // The sink-hole. void f(...) { } f(1); // Calls void f(...) { }
template<bool B, class T = void> // Default template version. struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it. template<class T> // A specialisation used if the expression is true. struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access. template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj) { return obj.serialize(); } template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj) { return to_string(obj); }
declval
是一个实用工具,它为您提供了一个对于一个不易构造的types的对象的“假引用”。 declval
对于我们的SFINAE结构非常方便。
struct Default { int foo() const {return 1;} }; struct NonDefault { NonDefault(const NonDefault&) {} int foo() const {return 1;} }; int main() { decltype(Default().foo()) n1 = 1; // int n1 // decltype(NonDefault().foo()) n2 = n1; // error: no default constructor decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2 std::cout << "n2 = " << n2 << '\n'; }