早在C++03的时候就用模板的方式实现过类成员函数的静态检查;这两天一个小兄弟又问到我同样问题,使用C++11撸了一个通用版本可以检查变量,函数和类型定义,使用GCC和CLANG编译都没问题,但在VS2015上却出现了迷之错误,前后折腾了半天确定应该是其BUG,重新选定方案撸了另一个版本,全平台兼容,点此下载。
需求
还是从需求出发:
- 简单易用,大概形式应该这样:HAS_MEMBER(type, name)
- 要同时支持成员变量,成员函数和类型定义,如: HAS_MEMBER(std::vector<int>, size), HAS_MEMBER(std::vector<int>, value_type)
- 引入函数定义支持重载函数,函数定义为了书写方便采用引用方式,如: HAS_MEMBER(std::vector<int>, push_back, void(const int&));对于非重载函数也可检验其是否符合函数定义,如: HAS_MEMBER(std::vector<int>, size, size_t())为false (正确应为: size_t() const)
测试先行:
HAS_MEMBER_DEF(push_back); HAS_MEMBER_DEF(size); HAS_MEMBER_DEF(value_type); HAS_MEMBER_DEF(first); HAS_MEMBER_DEF(x); int main() { static_assert(HAS_MEMBER(std::vector<int>, push_back) == false, "重载函数"); static_assert(HAS_MEMBER(std::vector<int>, push_back, void(const int &)), "重载函数指定函数类型"); static_assert(HAS_MEMBER(std::vector<int>, size), "非重载函数"); static_assert(HAS_MEMBER(std::vector<int>, size, size_t()) == false, "非重载函数带错误函数类型"); static_assert(HAS_MEMBER(std::vector<int>, size, size_t()const), "非重载函数带正确函数类型"); static_assert(HAS_MEMBER(std::vector<int>, value_type), "类型定义"); using IntIntPairType = std::pair<int, int>; // 解决宏分不清 "," 的问题 static_assert(HAS_MEMBER(IntIntPairType, first), "成员函数"); static_assert(HAS_MEMBER(IntIntPairType, x) == false, "不存在"); }
初始版本
这个实现在CLANG和GCC编译器下都OK,但在VS2015下会出现迷之错误,通过IDE智能提示查看到的值都是对的,但一编译就有问题,感觉应该是其decltype关键字实现上有BUG:
namespace detail { // 引入C++17中的std::void_t实现 template <class...> struct _Param_tester { using type = void; }; template <class... _Types> using void_t = typename _Param_tester<_Types...>::type; } #define HAS_MEMBER_DEF(name) \ namespace detail { \ template <class T, class F = void, class V = void> \ struct HasMember_##name : std::false_type {}; \ template <class T, class F> \ struct HasMember_##name<T, F, void_t<decltype(std::mem_fn<F>(&T::name))>> \ : std::true_type {}; \ template <class T> \ struct HasMember_##name<T, void, void_t<decltype(&T::name)>> \ : std::true_type {}; \ template <class T> \ struct HasMember_##name<T, void, void_t<typename T::name>> \ : std::true_type {}; \ } #define HAS_MEMBER(type, name, ...) \ detail::HasMember_##name<type, ##__VA_ARGS__, void>::value
终极实现
#define HAS_MEMBER_DEF(name) \ namespace detail { \ template <class T, class F = void> class HasMember_##name { \ template <class _Ux> \ static std::true_type \ Check(int, \ typename std::enable_if<std::is_void<F>::value, \ decltype(&_Ux::name) *>::type = nullptr); \ template <class _Ux> \ static std::true_type \ Check(int, decltype(std::mem_fn<F>(&_Ux::name)) * = nullptr); \ template <class _Ux> \ static std::true_type Check(int, typename _Ux::name * = nullptr); \ template <class _Ux> static std::false_type Check(...); \ \ public: \ static constexpr bool value = decltype(Check<T>(0))::value; \ }; \ } #define HAS_MEMBER(type, name, ...) \ detail::HasMember_##name<type, ##__VA_ARGS__>::value
更多实现
C++17引入的std::is_detected在大多数情况下,提供了解决这类问题的一个标准方式:
template<typename T> using toString_t = decltype( std::declval<T&>().toString() ); template<typename T> constexpr bool has_toString = std::is_detected_v<T, toString_t>;
此外Boost.TTI实现方式和我上面的类似:
#include <boost/tti/has_member_function.hpp> BOOST_TTI_HAS_MEMBER_FUNCTION(toString) ... constexpr bool foo = has_member_function_toString<T, std::string>::value;
使用
在C++14中(如果不使用template lambda可以兼容C++11,只是写起来要麻烦些),可以这样用:
template <bool C, class Ft, class Ff, class... Args> typename std::enable_if<C, size_t>::type Exec(Ft &&ft, Ff &&ff, Args &&... args) { return std::forward<Ft>(ft)(std::forward<Args>(args)...); } template <bool C, class Ft, class Ff, class... Args> typename std::enable_if<!C, size_t>::type Exec(Ft &&ft, Ff &&ff, Args &&... args) { return std::forward<Ff>(ff)(std::forward<Args>(args)...); } template <class T> auto Size(const T &obj) { // 使用了template lamabda需要使用C++14选项编译 return Exec<HAS_MEMBER(T, size)>( [](const auto &obj) { return obj.size(); }, [](const auto &obj) { return -1; }, obj); } int main() { std::vector<int> a; std::pair<int, int> b; assert(Size(a) == 0); assert(Size(b) == size_t(-1)); }
配合C++17的if constexpr特性,我们可以更优雅:
template<class T> size_t Size(const T& obj) { if constexpr (HAS_MEMBER(T, size)) return obj.size(); else return -1; }
后续
其实早在远古时代,MSVC就提供了非标准扩展__if_exists关键字来实现上述的功能,不仅仅能检查类成员,还可以检查全局函数,全局变量,局部变量,但我对非标的东西一直持排斥态度;于是实现了一个全局检测版本,在GCC,CLANG上不能编译,但在VS2015上能完美运行:
#define HAS_DEF(name) \ namespace detail { \ template <class F = void> class Has_##name { \ template <class _Ux> \ static std::true_type \ Check(int, typename std::enable_if<std::is_void<F>::value, \ decltype(&name) *>::type = nullptr); \ template <class _Ux> \ static std::true_type \ Check(int, decltype(std::bind<void, typename std::decay<F>::type>( \ &name)) * = nullptr); \ template <class _Ux> static std::false_type Check(...); \ \ public: \ static constexpr bool value = decltype(Check<void>(0))::value; \ }; \ } #define HAS(name, ...) detail::Has_##name<##__VA_ARGS__>::value #if defined(_MSC_VER) && (_MSC_VER >= 1900) void Print() {} void Print(int) {} void X() {} int aaa; HAS_DEF(Print) HAS_DEF(Print1) HAS_DEF(X) HAS_DEF(aaa) HAS_DEF(bbb) #endif int main() { #if defined(_MSC_VER) && (_MSC_VER >= 1900) static_assert(HAS(Print) == false, "重载函数"); static_assert(HAS(Print, void(int)), "重载函数指定函数类型"); static_assert(HAS(X), "非重载函数"); static_assert(HAS(X, void(int)) == false, "非重载函数带错误函数类型"); static_assert(HAS(X, void()), "非重载函数带正确函数类型"); static_assert(HAS(aaa), "变量"); static_assert(HAS(bbb) == false, "不存在"); #endif }
原始链接 版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 4.0