最近写代码的时候,总能在不少STL的函数声明里看见可爱的std::enable_if,虽然自己偶尔用过,不过也只是直接用STL库里的std::is_arithmetic之类。
于是就去Google了std::enable_if,然后参考了如下几篇post,写了一个较为通用的Module Check。
- Introduction to Type Traits in the C++ standard library
- SFINAE and enable_if
- Tips and tricks » EnableIf<C++11>
- C++技巧: SFINAE
- C++ metafunction to determine whether a type is callable
所以还是先从一个小的问题开始讲起吧~
假设我们做了一个中间类作为接口,这个中间类有一个模版构造函数,它接受一个类实例和一个double类型的参数,并且总是调用一个固定的实例方法。
class Intermediate { public: template <typename T> Intermediate(const T& object, double value) { object.method(value); } };
现在我们有如下的类,
class A { public: void method(double d) const { std::cout<<d<<'\n'; } }; class B { public: void method(int i) const { std::cout<<i<<'\n'; } };
虽然如下代码可以编译运行,但是假如我们希望只有类A被这样调用才有效呢?
Intermediate inter_a_1(a, 2.33); // print 2.33 Intermediate inter_b_1(b, 2.33); // print 2
如果仅仅是类A的话,我们或许还可以把构造函数写成这样
template <typename T> Intermediate(const T& object, double value) { if (std::is_same<T, A>::value) { object.method(value); } }
但如果要求变为“只要类T有method函数,并且参数类型只能是double”呢?
即使这个中间类只有自己内部使用,并且已知所有的 有method函数,并且参数类型只能是double 的类名字,用一大堆std::is_same也勉强算是一个解决办法(如果你不嫌麻烦和难以维护的话)。可要是这个类是作为SDK提供给用户的呢?总不能想着靠注释就能禁止掉错误的使用方式吧?(╯°□°)╯︵ ┻━┻
于是,这里就需要std::enable_if登场了(=゚ω゚)ノ
不过首先呢,我们还需要一个辅助的struct
template <typename T> struct module_check { typedef char (&no) [1]; typedef char (&yes) [2]; template <typename U, void (T::*)(double) const> struct function_match; template <typename U> static yes check(function_match<U, &U::method>*); // best match template <typename U> static no check(...); // other enum { value = (sizeof(check<T>(0)) == sizeof(yes)) }; };
首先typedef了yes和no,yes为2个字节,no为1个字节。然后这里利用了SFINAE,Substitution Failure Is Not An Error。即当我们的类T能够匹配上这个模版函数的话
template <typename U> static yes check(function_match<U, &U::method>*); // best match
check就将是2字节长的。
否则统统会匹配上下面那个模版函数,当匹配上下面那个模版函数时,check就将是1字节长的。最后我们判断check的size即可
value = (sizeof(check<T>(0)) == sizeof(yes))
现在可以请出我们的std::enable_if了,只需对刚才
Intermediate类的构造函数稍作改动即可。
template <typename T, typename = typename std::enable_if<module_check_<T>::value>::type> Intermediate(const T& object, double value) { if (std::is_same<T, A>::value) { object.method(value); } }
这是我们会发现,原来以类B初始化的语句已经无法编译了
那么我们能不能再把它写得更通用一点呢?当然是可以的,不过这就得用上宏了,但是宏的使用不在本文的讨论范围之内,所以就直接贴代码了。(其实就是注意观察需要变化的部分,然后写成参数就好了)
实际使用时的效果
#include <iostream> #include <type_traits> #include "module_check.h" class A { public: void method(double d) const { std::cout<<d<<'\n'; } }; class B { public: void method(int i) const { std::cout<<i<<'\n'; } }; module_check_t(perform_method_1, method); module_check(perform_method_2, method, int); class SmartClass { public: template <typename T, typename = typename std::enable_if<module_check_perform_method_1<T, void, double>::value>::type> SmartClass(const T& object_with_arg_type_double, double value) { object_with_arg_type_double.method(value); std::cout<<"SmartClass init with object which can performs method(double)\n"; } }; class StupidClass { public: template <typename T> StupidClass(const T& noob_object, double value) { noob_object.method(value); } }; int main(int argc, const char * argv[]) { std::cout<<module_check_perform_method_1<A, void, double>::value<<'\n'; // true std::cout<<module_check_perform_method_1<A, void>::value<<'\n'; // false due to arg type does not match std::cout<<module_check_perform_method_1<B, void, int>::value<<'\n'; // true std::cout<<module_check_perform_method_1<B, void>::value<<'\n'; // false due to arg type does not match A a; SmartClass smart1(a, 2.33); SmartClass smart2(a, 233); // 233 implicit cast to double StupidClass stupid1(a, 2.33); B b; // SmartClass smart3(b, 233); // this line won't compile // SmartClass smart4(b, 2.33); // this line won't compile StupidClass stupid2(b, 2.33); // 2.33 cast to int when method_1 called return 0; }
输出如下:
1 0 1 0 2.33 SmartClass init with object which can performs method(double) 233 SmartClass init with object which can performs method(double) 2.33 2
最后就是module_check.h了,最新的版本在我的Github上,module_check
// // module_check.h // module_check // // Created by Ryza 2016/8/16. // Copyright © 2016[data deleted]. All rights reserved. // #ifndef MODULE_CHECK_H #define MODULE_CHECK_H /** * @brief Define a function to check whether a given class can perform a specific method or not * * @discussion * For instance, we have such classes * class A { * public: * void method_1(); * }; * class B { * public: * void method_2(int); * }; * * To check whether they can perform method or not, we can write * * module_check(perform_method_1, method) * module_check(perform_method_2, method, int) * * The extra int behind 'method,' is the type of that method * * Then two structs named module_check_perform_method_1 and module_check_perform_method_2 will be generated * To use they * * if (module_check_perform_method_1<A, void>::value) { * // true * } * if (module_check_perform_method_1<A, int>::value) { * // false due to return type does not match * } * if (module_check_perform_method_1<B, void>::value) { * // false due to arg type does not match * } * * if (module_check_perform_method_2<B, void>::value) { * // true * } * if (module_check_perform_method_2<B, int>::value) { * // false due to return type does not match * } * if (module_check_perform_method_2<A, void>::value) { * // false due to arg type does not match * } * * Or * * class SmartClass { * public: * template <typename T, typename = typename std::enable_if<module_check_perform_method_2<T, void>::value, int>::type> * SmartClass(const T& object_with_arg_type_int) { * object_with_arg_type_int.method_1(233); * } * }; * * // A a; * // SmartClass smart1(a); // this line won't compile * * B b; * SmartClass smart2(b); * * @note This only applies to public method */ #define module_check(module_name, method_name, ...)\ template <typename T, typename R>\ struct module_check_##module_name {\ typedef char (&no) [1];\ typedef char (&yes) [2];\ template <typename U, R (T::*)(__VA_ARGS__)>\ struct function_match;\ template <typename U, R (T::*)(__VA_ARGS__) const>\ struct function_match_const;\ template <typename U>\ static yes check(function_match<U, &U::method_name>*);\ template <typename U>\ static yes check(function_match_const<U, &U::method_name>*);\ template <typename U>\ static no check(...);\ enum {\ value = (sizeof(check<T>(0)) == sizeof(yes))\ };\ }; /** * @brief A variant of module_check * * @discussion Define a function to check whether a given class can perform a specific method (only with const qualified) or not */ #define module_check_const(module_name, method_name, ...)\ template <typename T, typename R>\ struct module_check_##module_name {\ typedef char (&no) [1];\ typedef char (&yes) [2];\ template <typename U, R (T::*)(__VA_ARGS__) const>\ struct function_match_const;\ template <typename U>\ static yes check(function_match_const<U, &U::method_name>*);\ template <typename U>\ static no check(...);\ enum {\ value = (sizeof(check<T>(0)) == sizeof(yes))\ };\ }; /** * @brief Define a function to check whether a given class can perform a specific method or not * * @discussion * This macro acts pretty much like the one above, the only difference is that this accepts arg type in code * For instance, * class A { * public: * void method_1(double); * }; * class B { * public: * void method_1(int); * }; * * And we write * * module_check_t(perform_method_1, method) * * Then a struct named module_check_perform_method_1 will be generated * To use it * * if (module_check_perform_method_1<A, void, double>::value) { * // true * } * if (module_check_perform_method_1<A, void>::value) { * // false due to arg type does not match * } * if (module_check_perform_method_1<B, void, int>::value) { * // true * } * if (module_check_perform_method_1<B, void>::value) { * // false due to arg type does not match * } * * Or * * class SmartClass { * public: * template <typename T, typename = typename std::enable_if<module_check_perform_method_1<T, void, double>::value, int>::type> * SmartClass(const T& object_with_method_1_arg_type_double) { * object_with_method_1_arg_type_double.method_1(2.33); * } * }; * * A a; * SmartClass smart1(a); * * // B b; * // SmartClass smart2(b); // this line won't compile * * @note This only applies to public method */ #define module_check_t(module_name, method_name)\ template <typename T, typename R, typename ... Args>\ struct module_check_##module_name {\ typedef char (&no) [1];\ typedef char (&yes) [2];\ template <typename U, R (T::*)(Args ...)>\ struct function_match;\ template <typename U, R (T::*)(Args ...) const>\ struct function_match_const;\ template <typename U>\ static yes check(function_match<U, &U::method_name>*);\ template <typename U>\ static yes check(function_match_const<U, &U::method_name>*);\ template <typename U>\ static no check(...);\ enum {\ value = (sizeof(check<T>(0)) == sizeof(yes))\ };\ }; /** * @brief A variant of module_check_t * * @discussion Define a function to check whether a given class can perform a specific method (only with const qualified) or not */ #define module_check_const_t(module_name, method_name)\ template <typename T, typename R, typename ... Args>\ struct module_check_##module_name {\ typedef char (&no) [1];\ typedef char (&yes) [2];\ template <typename U, R (T::*)(Args ...) const>\ struct function_match_const;\ template <typename U>\ static yes check(function_match_const<U, &U::method_name>*);\ template <typename U>\ static no check(...);\ enum {\ value = (sizeof(check<T>(0)) == sizeof(yes))\ };\ }; #endif /* MODULE_CHECK_H */