我如何编写标准的C ++ 11库,或者为什么boost如此令人恐惧。 第4.2章

我们继续冒险。

先前部分的摘要


由于使用C ++ 11编译器的能力受到限制,并且由于缺乏替代性,boost希望在编译器随附的C ++ 98 / C ++ 03库之上编写自己的标准C ++ 11库实现。

实现了static_assertnoexceptcountof ,并且在考虑了所有非标准定义和编译器功能之后,出现了有关当前编译器支持的功能的信息。 包含了自己的nullptr实现,该实现在编译阶段选择。

现在是使用type_traits和所有这些“特殊模板魔术”的时候了。 在第一部分中,我们检查了我对标准库最简单模板的实现,但现在我们将更深入地了解这些模板。

链接到GitHub,为不耐烦的读者和非读者提供今天的结果:

欢迎有建设性的批评和批评

继续沉浸在“模板魔术” C ++世界中。

目录


引言
第1章。Viam supervadet vadens
第2章。#ifndef __CPP11_SUPPORT__#定义__COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
第3章。找到理想的nullptr实现
第4章C ++模板魔术
.... 4.1我们从小处着手
.... 4.2关于日志为我们编译了多少个奇迹般的错误
.... 4.3指针和所有所有
.... 4.4模板库还需要什么
第五章
...

第4章。模板“魔术” C ++。 延续性


4.2关于日志会编译多少个奇迹般的错误


在本章的第一部分中,介绍了基本的type_traits模板,但是完整模板中还缺少一些模板。

例如,仅需要is_integralis_floating_point模板,它们实际上是非常简单地定义的-通过每种内置类型的模板专用化。 这里的问题仅与long long的“大”类型有关。 事实是,这种内置类型仅从版本11开始出现在C ++语言标准中。 假设这一切都归结为检查C ++标准的版本( 无论如何都很难确定 ),这是合乎逻辑的,但是当时还没有。

图片 因为自1999年以来就存在C99 C语言标准,其中存在long long intunsigned long long int类型(自1999年以来!),并且由于C ++语言寻求保持与纯C的向后兼容性,因此许多编译器(通常在C ++ 03标准发布之前就将其添加为基本类型。 也就是说,情况是内置类型实际上是(来自C)的,但是在C ++标准中没有描述,因此不应存在。 这就给标准库的实现增加了一些混乱。 但是让我们看一下代码:

namespace detail { template <class> struct _is_floating_point : public false_type {}; template<> struct _is_floating_point<float> : public true_type {}; template<> struct _is_floating_point<double> : public true_type {}; template<> struct _is_floating_point<long double> : public true_type {}; } template <class _Tp> struct is_floating_point : public detail::_is_floating_point<typename remove_cv<_Tp>::type> { }; 

上面的代码对所有内容都很清楚-我们专门为必需的浮点类型设计了模板,并且在“清除”类型修饰符之后,对传递给我们的类型说“是”或“否”。 接下来的是整数类型:

 namespace detail { template <class> struct _is_integral_impl : public false_type {}; template<> struct _is_integral_impl<bool> : public true_type {}; template<> struct _is_integral_impl<char> : public true_type {}; template<> struct _is_integral_impl<wchar_t> : public true_type {}; template<> struct _is_integral_impl<unsigned char> : public true_type {}; template<> struct _is_integral_impl<unsigned short int> : public true_type {}; template<> struct _is_integral_impl<unsigned int> : public true_type {}; template<> struct _is_integral_impl<unsigned long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<unsigned long long int> : public true_type {}; #endif template<> struct _is_integral_impl<signed char> : public true_type {}; template<> struct _is_integral_impl<short int> : public true_type {}; template<> struct _is_integral_impl<int> : public true_type {}; template<> struct _is_integral_impl<long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<long long int> : public true_type {}; #endif template <class _Tp> struct _is_integral : public _is_integral_impl<_Tp> {}; template<> struct _is_integral<char16_t> : public true_type {}; template<> struct _is_integral<char32_t> : public true_type {}; template<> struct _is_integral<int64_t> : public true_type {}; template<> struct _is_integral<uint64_t> : public true_type {}; } template <class _Tp> struct is_integral : public detail::_is_integral<typename remove_cv<_Tp>::type> { }; 

在这里,您需要停下来思考一下。 对于“旧”整数类型,例如intbool等。 我们做与is_floating_point相同的专业化。 对于long类型的long long int及其无符号的对等类型,只有在C ++ 11中定义了LLONG_MAX define时 ,我们才定义重载(作为与C99兼容的第一个C ++标准),并且应在climits头文件中将其定义为max可以装入long long int类型的对象的大量数字。 Climits还具有其他一些宏定义(用于尽可能小的数目和无符号等效项),但是我决定使用此宏,这并不重要。 重要的是,与boost不同,在这种实现中,尽管C(可能)存在于编译器中,但是C中的“大”类型将不定义为整数常量。 更重要的是类型char16_tchar32_t ,它们也是C ++ 11中引入的,但尚未在C99中交付(它们已经与C ++在C11标准中同时出现),因此,在旧标准中,它们的定义可以只能通过类型别名(例如, typedef short char16_t ,但稍后再介绍)。 如果是这样,为了使模板专业化能够正确处理当这些类型是单独的(内置)以及通过typedef定义它们时的情况, 则还需要一层模板专业化细节:: _ is_integral

一个有趣的事实是,在某些旧的编译器中,这些C害羞的“大”类型不是整数常量 。 可以理解,甚至可以原谅,因为对于C ++直到11个标准,这些类型都是非标准的,因此通常不应该存在。 但是,很难理解的是,据推测,C ++ 11支持的最新的Embarcadero创意C ++编译器(Embarcadero C ++ Builder)中的这些类型在其32位程序集中仍然不是整数常数 (就像20年前一样)然后是Borland仍然正确)。 显然是因为这个原因,这些32位程序集中缺少大多数标准C ++ 11库(#include ratio?Chrono?将花费)。 Embarcadero似乎决定以座右铭强行推动64位时代:“您要C ++ 11还是更新的标准? 建立一个64位程序(只有clang,我们的编译器不能)!”

在完成了基本语言类型的讨论之后,我们介绍了一些更简单的模式:

简单图案
 template <bool, class _Tp = detail::void_type> struct enable_if { }; template <class _Tp> struct enable_if<true, _Tp> { typedef _Tp type; }; template<class, class> struct is_same : public false_type { }; template<class _Tp> struct is_same<_Tp, _Tp> : public true_type//specialization { }; template <class _Tp> struct is_const : public false_type { }; template <class _Tp> struct is_const<const _Tp> : public true_type { }; template <class _Tp> struct is_const<const volatile _Tp> : public true_type { }; /// is_volatile template<class> struct is_volatile : public false_type { }; template<class _Tp> struct is_volatile<volatile _Tp> : public true_type { }; template<class _Tp> struct is_volatile<const volatile _Tp> : public true_type { }; 


此处仅注意模板专门针对所有类型的修饰符(例如volatileconst volatile )的事实,因为 一些编译器在扩展模板时倾向于“丢失”修饰符之一。

另外,我着重强调了is_signedis_unsigned的实现:

 namespace detail { template<bool> struct _sign_unsign_chooser; template<class _Tp> struct _signed_comparer { static const bool value = _Tp(-1) < _Tp(0); }; template<class _Tp> struct _unsigned_comparer { static const bool value = _Tp(0) < _Tp(-1); }; template<bool Val> struct _cat_base : integral_constant<bool, Val> { // base class for type predicates }; template<> struct _sign_unsign_chooser<true>//integral { template<class _Tp> struct _signed : public _cat_base<_signed_comparer<typename remove_cv<_Tp>::type>::value> { }; template<class _Tp> struct _unsigned : public _cat_base<_unsigned_comparer<typename remove_cv<_Tp>::type>::value> { }; }; template<> struct _sign_unsign_chooser<false>//floating point { template<class _Tp> struct _signed : public is_floating_point<_Tp> { }; template<class _Tp> struct _unsigned : public false_type { }; }; } template<class T> struct is_signed { // determine whether T is a signed type static const bool value = detail::_sign_unsign_chooser<is_integral<T>::value>::template _signed<T>::value; typedef const bool value_type; typedef integral_constant<bool, is_signed::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; template<class T> struct is_unsigned { // determine whether T is an unsigned type static const bool value = detail::_sign_unsign_chooser<is_integral<T>::value>::template _unsigned<T>::value; typedef const bool value_type; typedef integral_constant<bool, is_unsigned::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; 

在实现这一部分时,我与Borland C ++ Builder 6.0展开了不平等的竞争,后者不想使这两个模板成为“ integral_constant”的继承人,这最终导致数十个内部编译器错误“模仿”了这些模板的“ integrated_constant”行为。 在这里,也许值得继续努力,并通过模板提出某种类型为is_ * un * signed:integer_constant的棘手派生,但是到目前为止,我没有优先考虑推迟此任务。 上面的代码部分有趣的是,在编译时如何确定类型是未签名的/已签名的。 首先,所有非整数类型都被标记,对于它们,模板进入带有模板参数false的单独的专用分支_sign_unsign_chooser ,对于标准浮点类型以外的所有类型,其反过来总是返回value == false (出于显而易见的原因,它们总是很重要的,因此_signed :: value将为true )。 对于整数类型,将执行简单但有趣的检查。 在这里,我们使用以下事实:对于无符号整数类型,当数字减小然后经过最小值(显然为0)时,会发生溢出,并且数字将获得其最大可能值。

这个事实是众所周知的,而且对于符号类型溢出是未定义的行为 ,您需要注意(根据标准,您不能将int变量减小到小于INT_MIN的事实,并希望由于溢出而得到INT_MAX而不是42或格式化的硬盘) )

我们使用这个事实写_Tp(-1)<_Tp(0)来检查“符号”类型,然后对无符号类型-1通过溢出将其“转换”为该类型的最大数目,而对于有符号类型,将进行这种比较而不会发生溢出, -1将与0进行比较。

今天的最后一个,但与我库中的最后一个“技巧”相去甚远的是alignment_of的实现:

 namespace detail { template <class _Tp> struct _alignment_of_trick { char c; _Tp t; _alignment_of_trick(); }; template <unsigned A, unsigned S> struct _alignment_logic_helper { static const std::size_t value = A < S ? A : S; }; template <unsigned A> struct _alignment_logic_helper<A, 0> { static const std::size_t value = A; }; template <unsigned S> struct _alignment_logic_helper<0, S> { static const std::size_t value = S; }; template< class _Tp > struct _alignment_of_impl { #if _MSC_VER > 1400 // // With MSVC both the build in __alignof operator // and following logic gets things wrong from time to time // Using a combination of the two seems to make the most of a bad job: // static const std::size_t value = (_alignment_logic_helper< sizeof(_alignment_of_trick<_Tp>) - sizeof(_Tp), __alignof(_Tp) >::value); #else static const std::size_t value = (_alignment_logic_helper< sizeof(_alignment_of_trick<_Tp>) - sizeof(_Tp), sizeof(_Tp) >::value); #endif typedef integral_constant<std::size_t, std::size_t(_alignment_of_impl::value)> type; private: typedef intern::type_traits_asserts check; typedef typename check::alignment_of_type_can_not_be_zero_assert< _alignment_of_impl::value != 0 >:: alignment_of_type_can_not_be_zero_assert_failed check1; // if you are there means aligment of type passed can not be calculated or compiler can not handle this situation (sorry, nothing can be done there) }; // borland compilers seem to be unable to handle long double correctly, so this will do the trick: struct _long_double_wrapper{ long double value; }; } template <class _Tp> struct alignment_of: public detail::_alignment_of_impl<_Tp>::type {}; template <class _Tp> struct alignment_of<_Tp&>: public alignment_of<_Tp*> {}; template<> struct alignment_of<long double>: public alignment_of<detail::_long_double_wrapper> {}; 

Microsoft的Visual Studio在这里再次表现出色,即使拥有内置的非标准__alignof内置宏,使用它时仍会产生不正确的结果。

升压说明
Visual C ++用户应注意,MSVC对“对齐”有不同的定义。 例如,考虑以下代码:

 typedef long long align_t; assert(boost::alignment_of<align_t>::value % 8 == 0); align_t a; assert(((std::uintptr_t)&a % 8) == 0); char c = 0; align_t a1; assert(((std::uintptr_t)&a1 % 8) == 0); 

在此代码中,即使boost :: alignment_of <align_t>报告align_t具有8字节对齐方式,但由于a1不在8字节边界上对齐,最终断言对于32位构建将失败。 请注意,如果使用MSVC固有的__alignof代替boost :: alignment_of,我们仍然会得到相同的结果。 实际上,对于MSVC对齐要求(和承诺)仅真正适用于动态存储,不适用于堆栈。


让我提醒您一下std :: alignment_of模板应该怎么做-返回一个值,该值表示此类型的元素在内存中的放置要求。 稍微分散注意力,然后每种类型的元素都会进行某种形式的内存分配,并且如果它对于元素数组是连续的,那么例如,类很可能在类的成员元素之间存在“空洞”( sizeofstruct { char a;}很可能不等于1,尽管里面的所有内容都为1字节,因为在优化过程中编译器会将其对齐为1 + 3字节)。

现在,让我们再次看一下代码。 我们声明_alignment_of_trick结构,在其中将要检查类型的元素(带有“缩进”)放入1个字节的内存中。 并通过简单地从结果结构的大小中减去要检查的类型的大小来检查对齐方式。 也就是说,如果编译器决定在要检查的类型的元素和先前的char之间“粘贴”一个空白,那么我们将在结构中获得类型对齐值。

同样,这里首先遇到静态断言作为类型。 它们声明为:

 namespace intern { // since we have no static_assert in pre-C++11 we just compile-time assert this way: struct type_traits_asserts { template<bool> struct make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert; template<bool> struct make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert; template<bool> struct not_allowed_arithmetic_type_assert; template<bool> struct alignment_of_type_can_not_be_zero_assert; }; template<> struct type_traits_asserts::make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert<true> { typedef bool make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert_failed; }; template<> struct type_traits_asserts::make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert<true> { typedef bool make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert_failed; }; template<> struct type_traits_asserts::not_allowed_arithmetic_type_assert<true> { typedef bool not_allowed_arithmetic_type_assert_failed; }; template<> struct type_traits_asserts::alignment_of_type_can_not_be_zero_assert<true> { typedef bool alignment_of_type_can_not_be_zero_assert_failed; }; } 

实际上,需要这些专用模板来替换C ++ 11中位于类定义内的static_assert 。 这种断言比第2章中STATIC_ASSERT的常规实现更轻巧,更专业,并且允许您不要将头文件core.h拖到type_traits中

图片 图案很多吗? 还会有更多! 我们现在将对此进行详细介绍,因为有关将模板编程与SFINAE技术相结合的有趣故事以及为什么我必须编写一个小型代码生成器的故事将继续下去。

谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN417949/


All Articles