C++ SFINAE examples?
我想进入更多的模板元编程。我知道sfinae代表"替换失败不是错误",但是有人能告诉我sfinae的好用法吗?
我喜欢使用
1 2 3 4 5 6 7 | 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 */ } |
它可能非常有用。例如,我使用它来检查使用运算符逗号收集的初始值设定项列表是否不再是固定大小
1 2 3 4 5 | template<int N> struct Vector { template<int M> Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ } } |
仅当m小于n时才接受该列表,这意味着初始值设定项列表没有太多元素。
语法
用
1 2 3 4 5 6 | template<int N> struct Vector { template<int M> Vector(MyInitList<M> const& i, typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ } } |
在实践中,我经常发现检查条件的能力是一种有用的能力。
以下是一个例子(从这里开始):
1 2 3 4 5 6 7 8 9 10 11 12 | 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 }; }; |
当计算
在C++ 11中,SIMEAE测试变得更加美观。以下是一些常用的例子:
根据特性选择函数过载
1 2 3 4 5 6 7 8 | 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 } |
使用所谓的类型接收器习语,您可以对类型执行相当任意的测试,比如检查它是否有成员,以及该成员是否属于某个类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //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和tag-dispatch的文章http://metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html
注意到C++ 14中有一个STD::VoIDyt,它本质上和我的TypeSink在这里是一样的。
Boost的Enable_if库为使用sfinae提供了一个干净的界面。我最喜欢的用法示例之一是boost.iterator库。sfinae用于启用迭代器类型转换。
C++ 17可能提供了一种通用的查询特征的方法。有关详细信息,请参见N4502,但作为一个独立的示例,请考虑以下内容。
这部分是常量部分,放在标题中。
1 2 3 4 5 6 7 8 9 10 11 | // 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,显示了用法:
1 2 3 4 5 6 7 | // 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>; |
与其他实现相比,这个实现相当简单:一组简化的工具(
下面是一个实况示例,其中包括GCCPre5.1的可移植性调整。
下面是另一个(后期)斯芬娜的例子,基于格雷格·罗杰斯的回答:
1 2 3 4 5 6 7 8 9 10 | 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); |
这样,您可以检查
1 2 3 4 5 | int main(void) { std::cout << IsClassT<std::string>::value << std::endl; // true std::cout << IsClassT<int>::value << std::endl; // false return 0; } |
这里有一篇很好的SFIEAE文章:C++的SFIEAE概念的介绍:类成员的编译时内省。
总结如下:
1 2 3 4 5 6 7 8 9 10 11 12 | /* 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(...) { } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 << ' '; } |
自上次对此线程的答复后,存在一个新的日志。
它是流畅的C++:HTTP:/FLUENTCPP.COM/
关于"sfinae"的研究有很多例子。