建设银行忘记密码网站首页,网站开发费用一般为多少,怎么看网站建设,手机分销网站建设1.概述 关于C11模板元的基本用法和常用技巧#xff0c;我在程序员2015年2月B《C11模版元编程》一文#xff08;后称前文#xff09;中已经做了详细地介绍#xff0c;那么C11模版元编程用来解决什么实际问题呢#xff0c;在实际工程中又该如何应用呢#xff1f;本文将侧重…1.概述 关于C11模板元的基本用法和常用技巧我在程序员2015年2月B《C11模版元编程》一文后称前文中已经做了详细地介绍那么C11模版元编程用来解决什么实际问题呢在实际工程中又该如何应用呢本文将侧重介绍C11模板的一些具体应用向读者展示模版元编程的具体应用。 我们将展示如何通过C11模版元来实现function_traits、Vairant类型和泛型bind绑定器。function_traits侧重于如何萃取可调用对象的一些元信息Variant则是一种能接受多种类型数据的“万能”类型bind则是一个泛化的绑定器下面来看看这些具体的例子。 2.function_traits function_traits用来获取函数语义的可调用对象的一些属性比如函数类型、返回类型、函数指针类型和参数类型等。下面来看看如何实现function_traits。 templatetypename T
struct function_traits;//普通函数
templatetypename Ret, typename... Args
struct function_traitsRet(Args...)
{
public:enum { arity sizeof...(Args) };typedef Ret function_type(Args...);typedef Ret return_type;using stl_function_type std::functionfunction_type;typedef Ret(*pointer)(Args...);templatesize_t Istruct args{static_assert(I arity, index is out of range, index must less than sizeof Args);using type typename std::tuple_elementI, std::tupleArgs...::type;};
};//函数指针
templatetypename Ret, typename... Args
struct function_traitsRet(*)(Args...) : function_traitsRet(Args...){};//std::function
template typename Ret, typename... Args
struct function_traitsstd::functionRet(Args...) : function_traitsRet(Args...){};//member function
#define FUNCTION_TRAITS(...) \template typename ReturnType, typename ClassType, typename... Args\struct function_traitsReturnType(ClassType::*)(Args...) __VA_ARGS__ : function_traitsReturnType(Args...){}; \FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)//函数对象
templatetypename Callable
struct function_traits : function_traitsdecltype(Callable::operator()){}; 由于可调用对象可能是普通的函数、函数指针、lambda、std::function和成员函数所以我们需要针对这些类型分别做偏特化然后萃取出可调用对象的元信息。其中成员函数的偏特化稍微复杂一点因为涉及到cv符的处理这里通过定义一个宏来消除重复的模板类定义。参数类型的获取我们是借助于tuple将参数转换为tuple类型然后根据索引来获取对应类型。它的用法比较简单 templatetypename T
void PrintType()
{cout typeid(T).name() endl;
}
int main()
{std::functionint(int) f [](int a){return a; };//打印函数类型PrintTypefunction_traitsstd::functionint(int)::function_type(); //将输出int __cdecl(int)//打印函数的第一个参数类型PrintTypefunction_traitsstd::functionint(int)::args0::type();//将输出int//打印函数的返回类型PrintTypefunction_traitsdecltype(f)::return_type(); //将输出int//打印函数指针类型PrintTypefunction_traitsdecltype(f)::pointer(); //将输出int (__cdecl*)(int)
} 可以看到这个function_traits通过类型萃取可以很方便地获取可调用对象函数、函数指针、函数对象、std::function和lambda表达式的一些元信息功能非常强大这个function_traits经常会用到是更高层模版元程序的基础。比如Variant类型的实现就要用到这个function_traits下面来看看Variant的实现。 3.Variant 借助上面的function_traits和前文实现的一些元函数我们就能方便的实现一个“万能类型”—VariantVariant实际上一个泛化的类型这个Variant和boost.variant的用法类似需要预定义一些类型作为可接受的类型。boost.variant的基本用法如下 typedef variantint,char, double vt;
vt v 1;
v a;
v 12.32; 这个variant可以接受已经定义的那些类型看起来有点类似于c#和java中的object类型实际上variant是擦除了类型要获取它的实际类型的时候就稍显麻烦需要通过boost.visitor来访问 struct VariantVisitor : public boost::static_visitorvoid
{void operator() (int a){cout int endl;}void operator() (short val){cout short endl;}void operator() (double val){cout double endl;}void operator() (std::string val){cout string endl;}
};boost::variantint,short,double,std::string v 1;
boost::apply_visitor(visitor, v); //将输出int 通过C11模版元实现的Variant将改进值的获取将获取实际值的方式改为内置的即通过下面的方式来访问 typedef Variantint, double, string, int cv;
cv v 10;
v.Visit([](double i){cout i endl; }, [](short i){cout i endl; }, [](int i){cout i endl; },[](const string i){cout i endl; });//结果将输出10 这种方式更方便直观。Variant的实现需要借助前文中实现的一些元函数MaxInteger、MaxAlign、Contains和At等等。下面来看看Variant实现的关键代码完整的代码请读者参考笔者在github上的代码https://github.com/qicosmos/cosmos/blob/master/Varaint.hpp。 templatetypename... Types
class Variant{enum{data_size IntegerMaxsizeof(Types)...::value,align_size MaxAlignTypes...::value};using data_t typename std::aligned_storagedata_size, align_size::type;
public:templateint indexusing IndexType typename Atindex, Types...::type;Variant(void) :m_typeIndex(typeid(void)){}~Variant(){ Destroy(m_typeIndex, m_data); }Variant(VariantTypes... old) : m_typeIndex(old.m_typeIndex){Move(old.m_typeIndex, old.m_data, m_data);}Variant(const VariantTypes... old) : m_typeIndex(old.m_typeIndex){Copy(old.m_typeIndex, old.m_data, m_data);}template class T,class typename std::enable_ifContainstypename std::remove_referenceT::type, Types...::value::type Variant(T value) : m_typeIndex(typeid(void)){Destroy(m_typeIndex, m_data);typedef typename std::remove_referenceT::type U;new(m_data) U(std::forwardT(value));m_typeIndex type_index(typeid(U));}templatetypename Tbool Is() const{return (m_typeIndex type_index(typeid(T)));}templatetypename Ttypename std::decayT::type Get(){using U typename std::decayT::type;if (!IsU()){cout typeid(U).name() is not defined. current type is m_typeIndex.name() endl;throw std::bad_cast();}return *(U*)(m_data);}templatetypename Fvoid Visit(F f){using T typename Function_TraitsF::template arg0::type;if (IsT())f(GetT());}templatetypename F, typename... Restvoid Visit(F f, Rest... rest){using T typename Function_TraitsF::template arg0::type;if (IsT())Visit(std::forwardF(f));elseVisit(std::forwardRest(rest)...);}
private:void Destroy(const type_index index, void * buf){std::initializer_listint{(Destroy0Types(index, buf), 0)...};}templatetypename Tvoid Destroy0(const type_index id, void* data){if (id type_index(typeid(T)))reinterpret_castT*(data)-~T();}void Move(const type_index old_t, void* old_v, void* new_v) {std::initializer_listint{(Move0Types(old_t, old_v, new_v), 0)...};}templatetypename Tvoid Move0(const type_index old_t, void* old_v, void* new_v){if (old_t type_index(typeid(T)))new (new_v)T(std::move(*reinterpret_castT*(old_v)));}void Copy(const type_index old_t, void* old_v, void* new_v){std::initializer_listint{(Copy0Types(old_t, old_v, new_v), 0)...};}templatetypename Tvoid Copy0(const type_index old_t, void* old_v, void* new_v){if (old_t type_index(typeid(T)))new (new_v)T(*reinterpret_castconst T*(old_v));}
private:data_t m_data;std::type_index m_typeIndex;//类型ID
}; 实现Variant首先需要定义一个足够大的缓冲区用来存放不同的类型的值这个缓类型冲区实际上就是用来擦除类型不同的类型都通过placement new在这个缓冲区上创建对象因为类型长度不同所以需要考虑内存对齐C11刚好提供了内存对齐的缓冲区aligned_storage template std::size_t Len, std::size_t Align /*default-alignment*/
struct aligned_storage; 它的第一个参数是缓冲区的长度第二个参数是缓冲区内存对齐的大小由于Varaint可以接受多种类型所以我们需要获取最大的类型长度保证缓冲区足够大然后还要获取最大的内存对齐大小这里我们通过前面实现的MaxInteger和MaxAlign就可以了Varaint中内存对齐的缓冲区定义如下 enum
{data_size IntegerMaxsizeof(Types)...::value,align_size MaxAlignTypes...::value
};using data_t typename std::aligned_storagedata_size, align_size::type; //内存对齐的缓冲区类型 其次我们还要实现对缓冲区的构造、拷贝、析构和移动因为Variant重新赋值的时候需要将缓冲区中原来的类型析构掉拷贝构造和移动构造时则需要拷贝和移动。这里以析构为例我们需要根据当前的type_index来遍历Variant的所有类型找到对应的类型然后调用该类型的析构函数。 void Destroy(const type_index index, void * buf){std::initializer_listint{(Destroy0Types(index, buf), 0)...};}templatetypename Tvoid Destroy0(const type_index id, void* data){if (id type_index(typeid(T)))reinterpret_castT*(data)-~T();} 这里我们通过初始化列表和逗号表达式来展开可变模板参数在展开的过程中查找对应的类型如果找到了则析构。在Variant构造时还需要注意一个细节是Variant不能接受没有预先定义的类型所以在构造Variant时需要限定类型必须在预定义的类型范围当中这里通过type_traits的enable_if来限定模板参数的类型。 template class T,class typename std::enable_ifContainstypename std::remove_referenceT::type, Types...::value::type Variant(T value) : m_typeIndex(typeid(void)){Destroy(m_typeIndex, m_data);typedef typename std::remove_referenceT::type U;new(m_data) U(std::forwardT(value));m_typeIndex type_index(typeid(U));} 这里enbale_if的条件就是前面实现的元函数Contains的值当没有在预定义的类型中找到对应的类型时即Contains返回false时编译期会报一个编译错误。 最后还需要实现内置的Vistit功能Visit的实现需要先通过定义一系列的访问函数然后再遍历这些函数遍历过程中判断函数的第一个参数类型的type_index是否与当前的type_index相同如果相同则获取当前类型的值。 templatetypename Fvoid Visit(F f){using T typename Function_TraitsF::template arg0::type;if (IsT())f(GetT());}templatetypename F, typename... Restvoid Visit(F f, Rest... rest){using T typename Function_TraitsF::template arg0::type;if (IsT())Visit(std::forwardF(f));elseVisit(std::forwardRest(rest)...);} Visit功能的实现利用了可变模板参数和function_traits通过可变模板参数来遍历一系列的访问函数遍历过程中通过function_traits来获取第一个参数的类型和Variant当前的type_index相同时则取值。为什么要获取访问函数第一个参数的类型呢因为Variant的值是唯一的只有一个值所以获取的访问函数的第一个参数的类型就是Variant中存储的对象的实际类型。 4.bind C11中新增的std::bind是一个很灵活且功能强大的绑定器std::bind用来将可调用对象与其参数进行绑定。绑定后的结果可以使用std::function进行保存并延迟调用到任何我们需要的时候。下面是它的基本用法 void output(int x, int y)
{std::cout x y std::endl;
}int main(void)
{std::bind(output, 1, 2)(); // 输出: 1 2std::bind(output, std::placeholders::_1, 2)(1); // 输出: 1 2std::bind(output, 2, std::placeholders::_1)(1); // 输出: 2 1
} std::placeholders::_1是一个占位符代表这个位置将在函数调用时被传入的第一个参数所替代。因为有了占位符的概念std::bind的使用非常灵活我们可以用它来替代任意位置的参数延迟到后面再传入实际参数。下图是bind的一个原理图更多的原理图读者可以参考http://blog.think-async.com/2010/04/bind-illustrated.html。 从上图中可以看到bind把参数和占位符保存起来了然后在后面调用的时候再按照顺序去替换占位符最终实现延迟执行。 我们可以通过模板元来实现一个简单的bind实现bind需要解决两个问题 1.将tuple展开为可变模板参数 bind绑定可调用对象时需要将可调用对象的形参可能含占位符保存起来保存到tuple中了。到了调用阶段我们就要反过来将tuple展开为可变参数因为这个可变参数才是可调用对象的形参否则就无法实现调用了。这里我们会借助于一个整形序列来将tuple变为可变参数在展开tuple的过程中我们还需要根据占位符来选择合适实参即占位符要替换为调用实参。这里要用到前文中实现的MakeIndexes。 2.根据占位符来选择合适的实参 这个地方比较关键因为tuple中可能含有占位符我们展开tuple时如果发现某个元素类型为占位符则从调用的实参生成的tuple中取出一个实参用来作为变参的一个参数当某个类型不为占位符时则直接从绑定时生成的形参tuple中取出参数用来作为变参的一个参数。最终tuple被展开为一个变参列表这时这个列表中没有占位符了全是实参就可以实现调用了。这里还有一个细节要注意替换占位符的时候如何从tuple中选择合适的参数呢因为替换的时候要根据顺序来选择。这里是通过占位符的模板参数I来选择因为占位符place_holderI的实例_1实际上place_holder1, 占位符实例_2实际上是palce_holder2,我们是可以根据占位符的模板参数来获取其顺序的。 下面来看看bind实现的关键代码完整的代码读者可以参考我github上的代码https://github.com/qicosmos/cosmos/blob/master/Bind.hpp。 template int I
struct Placeholder{};Placeholder1 _1; Placeholder2 _2; Placeholder3 _3; Placeholder4 _4; Placeholder5_5; Placeholder6 _6; Placeholder7 _7;Placeholder8 _8; Placeholder9 _9; Placeholder10 _10;template typename T, class Tuple
inline auto select(T val, Tuple)-T{return std::forwardT(val);
}template int I, class Tuple
inline auto select(PlaceholderI, Tuple tp) - decltype(std::getI - 1(tp)){return std::getI - 1(tp);
}template typename R, typename F, typename... P
inline typename std::enable_ifis_pointer_norefF::value, R::type invoke(F f, P... par){return (*std::forwardF(f))(std::forwardP(par)...);
}templatetypename Fun, typename... Args
struct Bind_t{typedef typename decayFun::type FunType;typedef std::tupletypename decayArgs::type... ArgType;typedef typename function_traitsFunType::return_type result_type;
public:templateclass F, class... BArgsBind_t(F f, BArgs... args) : m_func(f), m_args(args...){}templatetypename F, typename... BArgsBind_t(F f, BArgs... par) : m_func(std::move(f)), m_args(std::move(par)...){}template typename... CArgsresult_type operator()(CArgs... args){return do_call(MakeIndexesstd::tuple_sizeArgType::value::type(),std::forward_as_tuple(std::forwardCArgs(args)...));}templatetypename ArgTuple, int... Indexes result_type do_call(IndexTuple Indexes... in, ArgTuple argtp){return invokeresult_type(m_func, select(std::getIndexes(m_args),argtp)...);}private:FunType m_func;ArgType m_args;
};template typename F, typename... P
inline Bind_tF, P... Bind(F f, P... par){return Bind_tF, P...(std::forwardF(f), std::forwardP(par)...);
}template typename F, typename... P
inline Bind_tF, P... Bind(F f, P... par){return Bind_tF, P...(f, par...);
}
测试代码
void TestFun1(int a, int b, int c)
{
}void TestBind1()
{Bind(TestFun1, _1, _2, _3)(1, 2, 3);Bind(TestFun1, 4, 5, _1)(6);Bind(TestFun1, _1, 4, 5)(3);Bind(TestFun1, 3, _1, 5)(4);
} 由于只是展示bind实现的关键技术很多的实现细节并没有处理比如参数是否是引用、右值、cv符、绑定非静态的成员变量都还没处理仅仅用来展示如何综合运用一些模版元技巧和元函数并非是重复发明轮子只是展示bind是如何实现, 实际项目中还是使用c11的std::bind为好。 5总结 可以看到C11模板元编程能解决很复杂的问题能实现一些几乎不可能完成的功能比如实现了“万能”类型Variant和泛化的绑定器bind这些东西的实现如果不通过模版元的话几乎是无法想象的。虽然这些应用看起来比较复杂但是它将复杂性彻底的隐藏起来了通过眼花缭乱的模版元技巧来解决复杂的问题并最终给用户提供简单、强大和灵活的接口这也是模版元编程最有魅力也最令人着迷的地方。模版元编程的应用很广本文只是展示了一小部分的应用如果读者还希望了解更多的应用读还可以参考boost的mpl库和C11的源码。 备注本文是我发表在《程序员》2015.3月A转载请注明出处。