主页 > 游戏开发  > 

萃取的实现(三)

萃取的实现(三)
探测成员

        基于SFINAE,判断一个给定类型T,是否含有名为x的成员。

探测类型成员

        判断一个给定类型T,是否含有类型成员size_type,源码如下:

#include <type_traits> #include <iostream> #include <vector> template <typename ...> using void_t = void; template <typename , typename = void_t<>> struct has_sizetype_t : std::false_type {}; template <typename T> struct has_sizetype_t<T, void_t<typename T::size_type>> : std::true_type {}; int main(int argc, char **argv) { std::cout << (has_sizetype_t<int>::value ? "true" : "false") << std::endl; std::cout << (has_sizetype_t<std::vector<int>>::value ? "true" : "false") << std::endl; return 0; }

        注意:如果类型成员为private成员,萃取模板没有访问该类型的特殊权限,has_sizetype_t会返回false。

        对于普通类型,上述模板能够准确做出判断,但如果模板参数为引用类型,如std::vector<int>&,上述模板处理结果确是false。为了能兼容这种情况,可以在偏特化使用std::removereference,如下:

template <typename T> struct has_sizetype_t<T, void_t<typename std::remove_reference<T>::type::size_type>> : std::true_type {};

        对于注入类的名字,上述检测模板也会返回true,如下:

struct size_type {}; struct size_able : size_type {}; //... std::cout << (has_sizetype_t<size_able>::value ? "true" : "false") << std::endl; 探测任意类型成员

         has_sizetype_t能够有效的判断一个类型T是否包含类型成员size_type,如果把size_type换成其他类型呢?最简单也是最暴力的做法,就是仿照has_sizetype_t再实现一个萃取模板。但为每个类型成员都实现一个萃取模板,不太合理,把类型成员参数化,才是最优的选择。然而不幸的是,目前还没有语言机制可以被用来描述“潜在” 的名字。但是可以通过宏来实现这一功能,源码如下:

#include <type_traits> #include <iostream> #include <string> #include <vector> template <typename ...> using void_t = void; #define define_has_member_type(member_type) \ template <typename , typename = void_t<>> \ struct has_##member_type##_t : std::false_type {}; \ \ template <typename T> \ struct has_##member_type##_t<T, void_t<typename std::remove_reference<T>::type::member_type>> : std::true_type {}; //struct has_##member_type##_t<T, void_t<typename T::member_type>> : std::true_type {}; define_has_member_type(size_type) define_has_member_type(str_type) class mystring { public: using str_type = std::string; }; class myint { public: using int_type = int; }; int main(int argc, char **argv) { std::cout << (has_str_type_t<mystring&&>::value ? "true" : "false") << std::endl; std::cout << (has_str_type_t<myint>::value ? "true" : "false") << std::endl; std::cout << (has_size_type_t<int>::value ? "true" : "false") << std::endl; std::cout << (has_size_type_t<std::vector<int>>::value ? "true" : "false") << std::endl; std::cout << (has_size_type_t<std::vector<int>&&>::value ? "true" : "false") << std::endl; return 0; } 探测非类型成员

        对上述萃取模板做简单的修改,便可以得到探测非类型成员的萃取模板,如下:

#include <type_traits> #include <iostream> template <typename ...> using void_t = void; #define define_has_member(member) \ template <typename, typename = void_t<>> \ struct has_##member##_t : std::false_type {}; \ \ template <typename T> \ struct has_##member##_t<T, void_t<decltype(&T::member)>> : std::true_type {}; enum myenum { failed = 0, succeeded = 1 }; class myclass { public: int value; myenum status; static int instance_number; }; class myclass2 { public: using status = myenum; }; define_has_member(value) define_has_member(status) define_has_member(instance_number) int main(int argc, char **argv) { std::cout << (has_status_t<myclass2>::value ? "true" : "false") << std::endl; std::cout << (has_status_t<std::true_type>::value ? "true" : "false") << std::endl; std::cout << (has_value_t<myclass>::value ? "true" : "false") << std::endl; std::cout << (has_status_t<myclass>::value ? "true" : "false") << std::endl; std::cout << (has_instance_number_t<myclass>::value ? "true" : "false") << std::endl; return 0; }

        偏特化模板参数中为什么使用void_t<decltype(&T::member)>,而非void_t<decltype(T::member)>?因为使用后者探测数据成员没有问题,但探测成员函数就无能为力了。使用该萃取模板,还需要注意以下几点:

成员必须是非类型成员以及非枚举成员,否则&T::member失效,所以has_kind_type_t,has_ultrasonic_t均检测失败

成员必须是public成员

member必须没有歧义,对于has_kind_t<bat>::value,has_name_t<bat>::value,has_move_t<bat>::value,编译其无法确认kind为mammals::kind,还是bird::kind,name为mammals::name,还是bird::name,move为mammals::move,还是bird::move,所以均检测失败

如果存在重载函数,检测失败,如bat拥有两个search版本,所以has_search_t检测失败

        针对重载函数探测失效的问题,目前的方案只能针对不同的函数实现不同的的萃取模板。如下:

template <typename, typename = void_t<>> struct has_search_void_t : std::false_type {}; template <typename T> struct has_search_void_t<T, void_t<decltype(std::declval<T>().search())>> : std::true_type {}; template <typename, typename = void_t<>> struct has_search_int_t : std::false_type {}; template <typename T> struct has_search_int_t<T, void_t<decltype(std::declval<T>().search(0))>> : std::true_type {};

        下面是两种失败的尝试方案:

//方案1 //ERROR: //: error: expected ')' // std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl; //to match this '(' // std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl; template <typename T, typename ...Args, typename = void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>> constexpr std::true_type has_search_t(void *); template <typename, typename ...Args> constexpr std::false_type has_search_t(...); //... std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl; //方案2 //ERROR: //template parameter pack must be the last template parameter // template <typename, typename ...Args, typename = void_t<>> //error: expected expression // struct has_search_t <T, ...Args, void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>: std::true_type {}; //expected unqualified-id // struct has_search_t <T, ...Args, void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>: std::true_type {}; template <typename, typename = void_t<>> struct has_search_void_t : std::false_type {}; template <typename T> struct has_search_void_t<T, void_t<decltype(std::declval<T>().search())>> : std::true_type {};

         除了类的类型成员,变量成员,函数成员外,还可以用于探测表达式,甚至是多个表达式的组合,下面是两个例子:

template <typename ...> using void_t = void; //表达式 template <typename, typename, typename = void_t<>> struct has_less_t : std::false_type {}; template <typename T1, typename T2> struct has_less_t<T1, T2, void_t<decltype(std::declval<T1>() < std::declval<T2>())>> : std::true_type {}; //表达式组合 template <typename, typename = void_t<>> struct has_calc_t : std::false_type {}; template <typename T> struct has_calc_t<T, void_t<decltype(std::declval<T>() + std::declval<T>(), std::declval<T>() - std::declval<T>(), std::declval<T>() * std::declval<T>(), std::declval<T>() / std::declval<T>())>> : std::true_type {}; 用泛型 Lambda 探测成员

        使用萃取的实现(二)【将泛型 Lambdas 用于 SFINAE】中的lambda表达式,也可以实现成员探测,源码如下:

constexpr auto has_size_type = is_valid([](auto &&x)->typename std::decay_t<decltype(x)>::size_type {}); template <typename T> using has_size_type_t = decltype(has_size_type(std::declval<T>())); constexpr auto has_begin = is_valid([](auto &&x)->decltype(x.begin()){}); //constexpr auto has_begin = is_valid([](auto &&x)->decltype(std::declval<decltype(x)>().begin()){}); template <typename T> using has_begin_t = decltype(has_begin(std::declval<T>())); int main(int argc, char **argv) { std::cout << (has_size_type_t<std::string>::value ? "true" : "false") << std::endl; std::cout << (has_size_type_t<int>::value ? "true" : "false") << std::endl; std::cout << (has_begin_t<std::string>::value ? "true" : "false") << std::endl; std::cout << (has_begin_t<int>::value ? "true" : "false") << std::endl; return 0; } 其它的萃取技术 If-Then-Else

        如何根据数值的大小,自动选择合适的整型类型?首先定义一个模板if_then_else模拟选择行为,该模板接收三个模板参数,并且第一个参数为bool常量表达式,剩余的两个参数为类型参数,由第一个参数的值决定哪个类型参数生效;然后,定义一个参数为整型数值的模板,调用if_then_else,推断出合适的类型(由于实现不知道数值的合适类型,因此模板参数的类型使用了auto,需要c++17才能支持)。代码如下:

template <bool COND, typename TrueType, typename FalseType> struct if_then_else { using type = TrueType; }; template <typename TrueType, typename FalseType> struct if_then_else<false, TrueType, FalseType> { using type = FalseType; }; template <auto N> struct small_int { using type = typename if_then_else<N <= std::numeric_limits<char> ::max(), char, typename if_then_else<N <= std::numeric_limits<short> ::max(), short, typename if_then_else<N <= std::numeric_limits<int> ::max(), int, typename if_then_else<N <= std::numeric_limits<long>::max(), long, typename if_then_else<N <= std::numeric_limits<long long>::max(), long long, void>::type>::type>::type>::type>::type; };

        需要注意的是,和常规的 C++ if-then-else 语句不同,在最终做选择之前,then 和 else 分支 中的模板参数都会被计算,因此两个分支中的代码都不能有问题,否则整个程序就会有问题。 

探测不抛出异常的操作

        有时候我们需要判断一些操作是否会抛出异常,这时我们需要用到noexcept操作符,该操作符对表达式进行编译时检查,如果表达式不抛出任何异常返回true,否则返回false。主要应用场景如下:

显示指明函数是否会抛出异常:noexcept和noexcept(true)等效,表示该函数不抛出任何异常;noexcept(false)表示有可能抛出异常阻止异常传播和扩散,出现异常直接调用std::terminate终止进程 #include <iostream> void func1() { throw 1; } void func2() { func1(); } void func3() noexcept { func1(); } int main(int argc, char **argv) { try { func1(); } catch(...) { std::cout << "func1" << std::endl; } try { func2(); } catch(...) { std::cout << "func2" << std::endl; } try { func3(); //阻止异常传播,程序crash } catch(...) { std::cout << "func3" << std::endl; } return 0; } 用于模板,增强c++泛型能力 #include <type_traits> #include <iostream> template <typename T, typename = std::void_t<>> struct has_noexcept_move_constructor : std::false_type {}; template <typename T> struct has_noexcept_move_constructor<T, std::void_t<decltype(T(std::declval<T>()))>> : std::bool_constant<noexcept(T(std::declval<T>()))> {}; class A { public: A(A &&) { throw 1; } }; class B { public: B(B &&) = default; }; int main(int argc, char **argv) { std::cout << (has_noexcept_move_constructor<A>::value ? "true" : "false") << std::endl; std::cout << (has_noexcept_move_constructor<B>::value ? "true" : "false") << std::endl; return 0; } 萃取的便捷性

        关于萃取最大的问题便是繁琐,对于萃取类型的使用需要添加::type后缀,而且在依赖上下文中,还需要一个typename前缀,例如:

template <typename T> void print_type_name() { std::cout << typeid(typename std::remove_reference<T>::type).name() << std::endl; } 别名模板和萃取

        使用using创建别名,可以简化类型名称,如下:

template <typename T> using rmref = typename std::remove_reference<T>::type; template <typename T> void print_type_name() { std::cout << typeid(rmref<T>).name() << std::endl; }

        从c++14开始,直接为之引入了相应的别名模板,以_t结尾,对于“typename std::remove_reference<T>::type”,其别名为“std::remove_reference_t<T>”。

变量模板和萃取

        对于返回数值的萃取需要使用一个::value来生成萃取的结果,如下:

template <typename T1, typename T2> void print_diff() { std::cout << (std::is_same<T1, T2>::value ? "true" : "false") << std::endl; }

        针对上面这种情况,可以使用constexpr修饰的变量模板进行简化(需要在C++14之后),如下:

template <typename T1, typename T2> constexpr bool issame = std::is_same<T1, T2>::value; template <typename T1, typename T2> void print_diff() { std::cout << (issame<T1, T2> ? "true" : "false") << std::endl; }

        从c++17开始,直接引入了与之对应的变量模板,以_v结尾,对于“std::is_same<T1, T2>::value”,对应的变量模板为“std::is_same_v<T1, T2>”。

类型分析 判断基础类型

        判断一个类型是否是基础类型,如int是否是基础类型。代码如下:

template <typename T> struct is_fundamental : std::false_type {}; template <> struct is_fundamental<int> : std::true_type {}; template <> struct is_fundamental<double> : std::true_type {}; int main(int argc, char **argv) { std::cout << (is_fundamental<double>::value ? "true" : "false") << std::endl; std::cout << (is_fundamental<int>::value ? "true" : "false") << std::endl; return 0; }

         为了能够正确的识别出每一个基础类型,只能针对每个基础类型实现一遍is_fundamental。通过宏尽管能少些一些代码,但本质上依旧是对基础类型的穷举。

#define IS_FUNDAMENTAL(T) template <> struct is_fundamental<T> : std::true_type {}; IS_FUNDAMENTAL(float) IS_FUNDAMENTAL(unsigned long) 判断复合类型

        复合类型是由其它类型构建出来的类型。简单的复合类型包含指针类型,左值以及右值引用 类型,指向成员的指针类型(pointer-to-member types),和数组类型。

//指针 template <typename T> struct is_pointer : std::false_type {}; template <typename T> struct is_pointer<T *> : std::true_type {}; //左值引用 template <typename T> struct is_lvalue_reference : std::false_type {}; template <typename T> struct is_lvalue_reference<T &> : std::true_type {}; //右值引用 template <typename T> struct is_rvalue_reference : std::false_type {}; template <typename T> struct is_rvalue_reference<T &&> : std::true_type {}; //数组 template <typename T> struct is_array : std::false_type {}; template <typename T> struct is_array<T[]> : std::true_type {}; template <typename T, std::size_t N> struct is_array<T[N]> : std::true_type {}; //指向成员的指针 template <typename T> struct is_member_pointer : std::false_type {}; template <typename T, typename C> struct is_member_pointer<T C::*> : std::true_type {}; struct S { int a = 10; int b = 20; float func1(void) { return 0; } float func2(void) { return 1; } }; using mem_data_ptr_t = int S::*; using mem_func_ptr_t = float S::*; int main(int argc, char **argv) { std::cout << (is_pointer<int>::value ? "true" : "false") << std::endl; std::cout << (is_pointer<int *>::value ? "true" : "false") << std::endl; int *pi = &argc; std::cout << (is_pointer<decltype(pi)>::value ? "true" : "false") << std::endl; std::cout << std::endl; std::cout << (is_lvalue_reference<int>::value ? "true" : "false") << std::endl; std::cout << (is_lvalue_reference<int &>::value ? "true" : "false") << std::endl; int &lri = argc; std::cout << (is_lvalue_reference<decltype(lri)>::value ? "true" : "false") << std::endl; std::cout << std::endl; std::cout << (is_rvalue_reference<int>::value ? "true" : "false") << std::endl; std::cout << (is_rvalue_reference<int &&>::value ? "true" : "false") << std::endl; std::cout << (is_rvalue_reference<decltype(std::list<int>())>::value ? "true" : "false") << std::endl; //不太确定为什么decltype(std::list<int>())得到的就不是右值引用类型 std::cout << typeid(decltype(std::list<int>())).name() << std::endl; std::list<int> li; std::cout << (is_rvalue_reference<decltype(std::move(li))>::value ? "true" : "false") << std::endl; std::cout << typeid(decltype(std::move(li))).name() << std::endl; std::cout << std::endl; std::cout << (is_array<int>::value ? "true" : "false") << std::endl; std::cout << (is_array<int[]>::value ? "true" : "false") << std::endl; std::cout << (is_array<int [3]>::value ? "true" : "false") << std::endl; int arr[10]; //int arr[0]; decltype(arr)得到的类型时A0_i,不是数组 std::cout << (is_array<decltype(arr)>::value ? "true" : "false") << std::endl; std::cout << typeid(int[3]).name() << std::endl; std::cout << typeid(decltype(arr)).name() << std::endl; std::cout << std::endl; using mem_string_ptr_t = std::string S::*; //虽然这个类型不存在,但结果依然返回了true,标注库意识如此 std::cout << (is_member_pointer<mem_string_ptr_t>::value ? "true" : "false") << std::endl; std::cout << (is_member_pointer<mem_data_ptr_t>::value ? "true" : "false") << std::endl; std::cout << (is_member_pointer<mem_func_ptr_t>::value ? "true" : "false") << std::endl; } 识别函数类型

        在讨论如何识别函数类型时,先考虑如何定义一个函数类型。下面是函数类型定义的相关代码:

#include <iostream> int func_instance(int arg) { std::cout << arg << std::endl; return 0; } int main(int argc, char **argv) { typedef int (func_t)(int); func_t *func1 = func_instance; ✅ func1(12); typedef int (*func_pt)(int); func_pt func2 = func_instance; ✅ func2(14); return 0; }

         通过上面的例子,可以看出:只要不同函数类型的返回值和入参一致,既可以表示同一个类型,名称不是特别重要。因此,识别函数类型时,只用到了返回值和入参,只要类型符合Ret(Args...)格式,该类型就是函数类型。这个格式很像去掉了名称的函数类型定义。下面是函数类型识别源码:

#include <type_traits> #include <iostream> #include <cstdio> template <typename T> struct is_function : std::false_type { constexpr static int flag = 0; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args...)> : std::true_type { constexpr static int flag = 1; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...)> : std::true_type { constexpr static int flag = 2; }; class A { public: A(){} static A &GetInstance() { static A _inst; return _inst; } void Set(int) {} int Get(void) const { return 0; } int Func(int a) const & { return a; } }; struct B { static int foo(); int fun() const&; }; int main(int argc, char **arg) { std::cout << (is_function<int(int)>::value ? "true" : "false") << " " << is_function<int(int)>::flag << std::endl; std::cout << (is_function<decltype(printf)>::value ? "true" : "false") << " " << is_function<decltype(printf)>::flag << std::endl; std::cout << (is_function<decltype(&A::Set)>::value ? "true" : "false") << " " << is_function<decltype(&A::Set)>::flag << std::endl; std::cout << (is_function<const int(void)>::value ? "true" : "false") << " " << is_function<const int(void)>::flag << std::endl; std::cout << (is_function<decltype(&A::Get)>::value ? "true" : "false") << " " << is_function<decltype(&A::Get)>::flag << std::endl; std::cout << (is_function<decltype(A::GetInstance)>::value ? "true" : "false") << " " << is_function<decltype(A::GetInstance)>::flag << std::endl; std::cout << (is_function<decltype(&B::fun)>::value ? "true" : "false") << " " << is_function<decltype(&B::fun)>::flag << std::endl; std::cout << (is_function<int(int)const>::value ? "true" : "false") << " " << is_function<int(int)const>::flag << std::endl; std::cout << (is_function<int(int) &>::value ? "true" : "false") << " " << is_function<int(int) &>::flag << std::endl; std::cout << (std::is_function<decltype(&A::Get)>::value ? "true" : "false") << std::endl; return 0; }

        需要注意几个问题:

is_function识别的是函数类型,对于函数需要通过decltype推断出其函数类型,如decltype(printf)is_function可以识别类的静态成员函数,无法识别非静态成员函数is_function不能处理所有函数类型,因为有些函数还包含const和volatile,到c++17之后,还包含noexcept操作符,如int(int) const此外还应该处理函数的引用情况,关于函数的引用,解释如下(摘自c 成员函数声明()后加&或&&表示什么):

C++中,成员函数声明后添加 & 或 && 表明这个成员函数是针对左值对象还是右值对象进行操作的。具体来说,在成员函数声明后加上 & 表示该成员函数只能被左值对象调用、而加上 && 表示该成员函数只能被右值对象调用。这种技术是C++11引入的,用于支持移动语义和更精细地控制对象的行为。

        更多is_function的重载,如下(非全部):

template <typename Ret, typename ...Args> struct is_function<Ret(Args...) const> : std::true_type { constexpr static int flag = 3; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...) const> : std::true_type { constexpr static int flag = 4; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args...) volatile> : std::true_type { constexpr static int flag = 5; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...) volatile> : std::true_type { constexpr static int flag = 6; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args...) const volatile> : std::true_type { constexpr static int flag = 7; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...) const volatile> : std::true_type { constexpr static int flag = 8; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args...) &> : std::true_type { constexpr static int flag = 9; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...) &> : std::true_type { constexpr static int flag = 10; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args...) const &> : std::true_type { constexpr static int flag = 11; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...) const &> : std::true_type { constexpr static int flag = 12; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args...) volatile &> : std::true_type { constexpr static int flag = 13; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...) volatile &> : std::true_type { constexpr static int flag = 14; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args...) const volatile &> : std::true_type { constexpr static int flag = 15; }; template <typename Ret, typename ...Args> struct is_function<Ret(Args..., ...) const volatile &> : std::true_type { constexpr static int flag = 16; };

        以上仅是基础实现,libc++,libstdc++,MS stl还有更为简单的实现,如下:

template<class T> struct is_function : std::integral_constant< bool, !std::is_const<const T>::value && !std::is_reference<T>::value > {}; 判断class类型

        原理:只有class类型才可以被用于指向成员的指针类型,即对于T Y::*一类的类型结构,Y只能是class类型,T可以选择任何类型。

#include <type_traits> #include <iostream> template <typename T, typename = std::void_t<>> struct is_class : std::false_type {}; template <typename T> struct is_class<T, std::void_t<int T::*>> : std::true_type {}; struct s {}; class c {}; union u {}; enum e {}; int main(int argc, char **argv) { std::cout << (is_class<s>::value ? "true" : "false") << std::endl; std::cout << (is_class<c>::value ? "true" : "false") << std::endl; std::cout << (is_class<u>::value ? "true" : "false") << std::endl; std::cout << (is_class<e>::value ? "true" : "false") << std::endl; auto f = []{}; std::cout << (is_class<decltype(f)>::value ? "true" : "false") << std::endl; std::cout << (is_class<uint64_t>::value ? "true" : "false") << std::endl; std::cout << (is_class<int>::value ? "true" : "false") << std::endl; return 0; }

        需要注意两点以下两点:

c++语言指出,lambda 表达式的类型是“唯一的,未命名的,非枚举 class 类型”,使用is_class萃取lambda表达式,得到的结果也是trueint T::*表达式同样适用于 union类型
标签:

萃取的实现(三)由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“萃取的实现(三)