概念:简化STD Utility类的实现


C ++ 20中出现的概念是一个长期且广泛讨论的主题。 尽管这些年来积累了过多的材料(包括世界一流专家的演讲),但应用程序程序员(他们每天都不睡着使用标准)仍然感到困惑,C ++ 20概念是什么?我们需要多年来是否检查过enable_if。 部分原因是概念在大约15年的时间内是如何演变的(概念完整+概念图->概念精简版),部分原因是这些概念与其他语言中的类似工具(Java / C#通用范围,Rust trait)不同。 ..)。


C ++ Russia 2019大会的ReSharper C ++团队的Andrey Davydov的报告的剪辑片段和录像带下。 Andrey简要回顾了C ++ 20的概念相关创新,之后他通过比较C ++ 17和C ++ 20解决方案,研究了STL的某些类和功能的实现。 这个故事进一步代表了他。



谈论概念。 这是一个相当复杂和广泛的主题,因此在准备报告时,我遇到了一些困难。 我决定转向C ++社区Andrei Alexandrescu最好的发言人之一的经验。


2018年11月,在会议C ++开幕式上,安德烈(Andrei)向听众询问了C ++的下一个主要功能是什么:


  • 概念
  • 元类
  • 还是内省?

让我们从这个问题开始。 您认为C ++的下一个重要功能是概念吗?


根据Alexandrescu所说,概念很无聊。 我建议您这样做很无聊。 而且,我仍然不能像Herb Sutter这样的元类或者像Alexandrescu这样的内省那样有趣而激烈地谈论。


当我们谈论C ++ 20中的概念时,我们指的是什么? 至少从2003年开始就对该功能进行了讨论,在此期间,它已经有了很大的发展。 让我们看看C ++ 20中出现了哪些与新概念相关的功能。


concept关键字定义了一个称为“ concepts”的新实体。 这是模板参数的谓词。 看起来像这样:


 template <typename T> concept NoThrowDefaultConstructible = noexept(T{}); template <typename From, typename To> concept Assignable = std::is_assignable_v<From, To> 

我不仅使用了“在模板参数上”一词,还没有在“类型上”一词,因为可以在非标准模板参数上定义概念。 如果您根本无事可做,则可以为数字定义一个概念:


 template<int I> concept Even = I % 2 == 0; 

但是混合使用典型的和非典型的模板参数更有意义。 如果类型的大小和对齐方式不超过指定的限制,则将其称为小类型:


 template<typename T, size_t MaxSize, size_t MaxAlign> concept Small = sizeof(T) <= MaxSize && alignof(T) <= MaxAlign; 

可能尚不明确,为什么我们需要用这种语言围起一个新实体,以及为什么这个概念不仅仅是constexpr bool变量。


 //  `concept`    ? #define concept constexpr bool 

如何使用概念?


要了解,让我们看看如何使用概念。


首先,就像constexpr bool变量一样,它们可以在编译时需要布尔表达式的任何地方使用。 例如,在static_assert内部或noexcept内部
规格:


 // bool expression evaluated in compile-time static_assert(Assignable<float, int>); template<typename T> void test() noexcept(NothrowDefaultConstructible<T>) { T t; ... } 

其次,在定义模板参数时,可以使用概念代替typenameclass关键字。 定义一个简单的optional类,该类将简单地存储一对initialized的布尔值和值。 自然,此类optional仅适用于琐碎的类型。 因此,我们在这里编写Trivial东西,当我们尝试从非琐碎的东西实例化时,例如从std::string实例化时,将出现编译错误:


 //  type-parameter-key (class, typename) template<Trivial T> class simple_optional { T value; bool initialized = false; ... }; 

概念可以部分应用。 例如,我们使用小缓冲区优化来实现我们的any类。 使用固定的SizeAlignment定义SB结构(小缓冲区),我们将SB并集和指针存储在堆上。 现在,如果在构造函数中使用小类型,则可以将其放入SB 。 为了确定类型是小类型,我们写道它满足Small的概念。 Small概念使用了3个模板参数:我们定义了两个,并从一个模板参数得到了一个函数:


 //   class any { struct SB { static constexpr size_t Size = ...; static constexpr size_t Alignment = ...; aligned_storage_t<Size, Alignment> storage; }; union { SB sb; void* handle; }; template<Small<SB::Size, SB::Alignment> T> any(T const & t) : sb(...) ... }; 

记录较短。 我们在auto之前写出模板参数的名称,可能带有一些参数。 前面的示例以这种方式重写:


 // Terse syntax (  auto) class any { struct SB { static constexpr size_t Size = ...; static constexpr size_t Alignment = ...; aligned_storage_t<Size, Alignment> storage; }; union { SB sb; void* handle; }; any(Small<SB::Size, SB::Alignment> auto const & t) : sb(...) ... }; 

可能在我们编写auto任何地方,现在您都可以在概念的前面写下它的名称。


定义get_handle函数,该函数返回对象的某些handle
我们假设小对象本身是handle ,而对于大对象,指向它们的指针是handle 。 因为if constexpr表示不同类型的表达式,我们有两个分支,所以对我们来说,不显式指定此函数的类型而是要求编译器输出它是方便的。 但是,如果我们仅auto ,我们将丢失指示值较小的信息,该值不会超出指针:


 //Terse syntax (  auto) template<typename T> concept LEPtr = Small<T, sizeof(void *), alignof(void *)>; template<typename T> auto get_handle(T& object) { if constexpr (LEPtr<T>) return object; else return &object; } 

在C ++ 20中,可以在它之前写出它不仅是auto ,而且是有限制的auto


 // Terse syntax (  auto) template<typename T> concept LEPtr = Small<T, sizeof(void *), alignof(void *)>; template<typename T> LEPtr auto get_handle(T &object) { if constexpr (LEPtr<T>) return object; else return &object; } 

需要表达


需要expression是整个expressionov族,它们都是bool类型,并且在编译时计算。 它们用于测试有关表达式和类型的语句。 需要表达对于定义概念非常有用。


Constructible示例。 我以前的报告中的那些人已经见过他:


 template<typename T, typename... Args> concept Constructible = requires(Args... args) { T{args...} }; 

并以Comparable为例。 假设如果可以使用“较少”运算符Comparable两个类型为T对象,并且结果转换为bool ,则类型T是可比较的。 此箭头及其后面的类型表示类型表达式已转换为bool ,而不是等于bool


 template<typename T> concept Comparable = requires(T const & a, T const & b) { {a < b} -> bool; }; 

我们研究的内容已经足够显示出概念使用的完整示例。


我们已经有一个Comparable概念,让我们为迭代器定义概念。 假设RandomAccessIteratorBidirectionalIterator和其他一些属性。 这样,我们定义了Sortable的概念。 如果可以比较RangeRandomAccess迭代器及其元素,则将Range称为Sortable 。 现在我们可以编写一个sort函数,该函数不仅接受该函数,还接受Sortable Range


 // concepts,    ++20 template<typename Iterator> concept RandomAccessIterator = BidirectionalIterator<Iterator> && ...; template<typename R> concept Sortable = RandomAccessIterator<Iterator<R>> && Comparable<ValueType<R>>; template<Sortable Range> void sort(Range &) {...} 

现在,如果我们尝试从不满足Sortable概念的对象中调用此函数,则会从编译器收到一条清晰的消息,指出对SFINAE友好的良好错误。 让我们尝试std::list无法比较的元素std::list '或向量来实例化std::list


 //concepts,    ++20,  struct X {}; void test() { vector<int> vi; sort(vi); // OK list <int> li; sort(li); // Fail, list<int>::iterator is not random access vector< X > vx; sort(vx); // Fail, X is not Comparable } 

您是否已经看过使用概念或类似内容的类似示例? 我已经看过几次了。 老实说,它根本没有说服我。 如果我们可以在C ++ 17中获得它,是否需要用这种语言来限制这么多新实体?


 //concepts,    ++17 #define concept constexpr bool template<typename T> concept Comparable = is_convertible_v< decltype(declval<T const &>() < declval<T const &>()), bool >; template<typename Iterator> concept RandomAccessIterator = BidirectionalIterator<Iterator> && ...; template<typename R> concept Sortable = RandomAccessIterator<Iterator<R>> && Comparable<ValueType<R>>; template<typename Range, typename = enable_if_t<Sortable<Range>>> void sort(Range &) { ... } 

concept宏输入了关键字concept ,并且Comparable以此方式进行了重写。 它变得有些丑陋,这向我们暗示要求表达确实是有用且方便的事情。 因此,我们定义了Sortable的概念,并使用enable_if表示sort函数接受Sortable Range


您可能会认为,根据编译错误消息,此方法会损失很多,但是,实际上,这与编译器实现的质量有关。 假设Clang在这个主题上大惊小怪,特别是跳过了如果您用enable_if替换了第一个参数
如果计算为false ,则他们会出现此错误,因此不满足此要求。


上面的示例似乎是通过概念编写的。 我有一个假设:此示例没有定论,因为它没有使用概念的主要功能-require子句。


要求条款


Requires子句几乎挂在任何模板声明或非模板函数上。 从语法上讲,这看起来像requires关键字,后跟一些布尔表达式。 为了筛选出模板特化或重载候选,这是必需的,也就是说,它与SFINAE的工作方式相同,只能正确完成,而不能被黑客入侵:


 // requires-clause template<typename R> concept Sortable = RandomAccessIterator<Iterator<R>> && Comparable<ValueType<R>>; template<Sortable Range> void sort(Range &) { ... } 

在排序后的示例中,我们可以在哪里使用require子句? 我们使用以下代码代替应用概念的简短语法:


 template<typename R> concept Sortable = RandomAccessIterator<Iterator<R>> && Comparable<ValueType<R>>; template<typename Range> requires Sortable<Range> void sort(Range &) { ... } 

似乎代码变得越来越差,变得越来越大。 但是现在我们可以摆脱Sortable概念了。 在我看来,这是一种改进,因为Sortable概念Sortable重言式的:我们将可以传递给sort函数的所有东西都称为Sortable 。 这没有物理意义。 我们以这种方式重写代码:


 //template<typename R> concept Sortable // = RandomAccessIterator<Iterator<R>> && Comparable<ValueType<R>>; template<typename Range> requires RandomAccessIterator<Iterator<Range>> && Comparable<ValueType<Range>>; void sort(Range &) { ... } 

概念相关功能的摘要列表


C ++ 20中与概念相关的创新的列表如下所示。 从我的主观角度来看,此列表中的项目是通过增加功能的实用性进行排序的:


  • 新的实体concept 。 在我看来,可以通过给constexpr bool变量赋予附加的语义来消除concept实质。
  • 应用概念的特殊语法。 当然,这很令人愉快,但这只是语法。 如果C ++程序员害怕语法错误,他们早就因恐惧而死。
  • 需要表达确实是一件很酷的事情,不仅对定义概念有用。
  • Requires子句是概念的最大价值,它使您可以忘记SFINAE和其他C ++模板的传奇恐怖。

更多内容需要表达


在我们进入对require子句的讨论之前,请先介绍一下require表达式。


首先,它们不仅可以用于定义概念。 从远古时代开始,Microsoft编译器的扩展名为__if_exists __if_not_exists 。 它允许编译时验证名称的存在,并根据此启用或禁用代码块的编译。 在几年前使用的代码库中,就是这样。 有一个函数f() ,它获取模板类型的一个点,并从该点开始获取高度。 它可以通过三维或二维点实例化。 对于三维,我们将z坐标视为高度,对于二维,我们转向特殊的表面传感器。 看起来像这样:


 struct Point2 { float x, y; }; struct Point3 { float x, y, z; }; template<typename Point> void f(Point const & p) { float h; __if_exists(Point::z) { h = pz; } __if_not_exists(Point::z) { h = sensor.get_height(p); } } 

在C ++ 20中,我们无需使用标准代码使用编译器扩展就可以重写此代码。 在我看来,情况并没有恶化:


 struct Point2 { float x, y; }; struct Point3 { float x, y, z; }; template<typename Point> void f(Point const & p) { float h; if constexpr(requires { Point::z; }) h = pz; else h = sensor.get_height(p); } 

第二点是您需要警惕需要表达的语法。
它们非常强大,通过引入许多新的句法结构可以实现这种功能。 至少在一开始,您可能会对它们感到困惑。


让我们定义一个Sizable概念,该概念检查容器是否具有返回size_t的恒定方法size 。 我们自然希望vector<int>Sizable ,但是static_assert 。 您了解我们为什么有错误吗? 为什么此代码无法编译?


 template<typename Container> concept Sizable = requires(Container const & c) { c.size() -> size_t; }; static_assert(Sizable<vector<int>>); // Fail 

让我向您展示编译的代码。 这样的X类满足Sizable概念。 现在您了解我们有什么问题吗?


 struct X { struct Inner { int size_t; }; Inner* size() const; }; static_assert(Sizable<X>); // OK 

让我修复突出显示的代码。 左边的代码是我想要的颜色。 但实际上,它应该画在右边:



看到,站在箭头后面的size_t的颜色已经改变了吗? 我希望它是一种类型,但这只是我们正在访问的字段。 我们需要表达的所有内容都是一个大表达,我们检查它的正确性。 对于类型X ,是,这是一个有效的表达式;对于vector<int> ,否。 为了实现我们想要的,我们需要将花括号括起来:


 template<typename Container> concept Sizable = requires(Container const & c) { {c.size()} -> size_t; }; static_assert(Sizable<vector<int>>); // OK struct X { struct Inner { int size_t; }; Inner* size() const; }; static_assert(Sizable<X>); // Fail 

但这只是一个有趣的例子。 通常,您只需要小心。


使用概念的例子


配对类的实现


此外,我将演示一些可以在C ++ 17中实现的STL片段,但相当麻烦。
然后我们将看到在C ++ 20中我们如何改进实现。


让我们从pair类开始。
这是一个非常古老的类,它仍然在C ++ 98中。
它不包含任何复杂的逻辑,因此
我希望他的定义看起来像这样。
从我的角度来看,它应该大致在此结束:


 template<typename F, typename S> struct pair { F f; S s; ... }; 

但是,根据cppreference ,一pair设计师只有8件。
而且,如果您查看实际的实现(例如,在Microsoft STL中),那么pair类的构造器将多达15个。 我们不会看所有这些功能并将自己局限于默认构造函数。


看来这很复杂? 首先,我们了解为什么需要它。 我们想要的是, pair类的参数之一是微不足道的类型,例如int ,那么在构造pair类之后,它会初始化为零,并且不会保持未初始化状态。 为此,我们要编写一个构造函数,该构造函数调用字段f (第一)和s (第二)的值初始化。


 template<typename F, typename S> struct pair { F f; S s; pair() : f() , s() {} }; 

不幸的是,如果我们尝试从没有默认构造函数的实例中实例化一pair ,例如从此类实例化,我们会立即收到编译错误。 理想的行为是,如果尝试构造pair ,则默认值为编译错误,但是如果我们显式传递fs的值,则一切都会起作用:


 struct A { A(int); }; pair<int, A> a2; // must fail pair<int, A> a1; { 1, 2 }; // must be OK 

为此,将默认构造函数设为模板并将其限制为SFINAE。
让我们想到的第一个想法是让我们编写,以便仅当fsis_default_constructable时才允许使用此构造函数:


 template<typename F, typename S> struct pair { F f; S s; template<typename = enable_if_t<conjunction_v< is_default_constructible<F>, // not dependent is_default_constructible<S> >>> pair() : f(), s() {} }; 

这将不起作用,因为enable_if_t参数仅取决于类的模板参数。 即,在替换类别之后,它们变得独立,可以立即对其进行计算。 但是,如果分别得到false ,则会再次遇到硬编译器错误。


为了克服这个问题,让我们向此构造函数添加更多模板参数,并使条件enable_if_t依赖于这些模板参数:


 template<typename F, typename S> struct pair { F f; S s; template<typename T = F, typename U = S, typename = enable_if_t<conjunction_v< is_default_constructible<T>, is_default_constructible<U> >>> pair() : f(), s() {} }; 

情况很可笑。 事实是模板参数TU不能由用户明确设置。 在C ++中,没有语法来显式设置构造函数的模板参数;它们不能由编译器输出,因为它无处可显示。 它们只能来自默认值。 也就是说,该代码实际上与前面示例中的代码没有区别。 但是,从编译器的角度来看,它是有效的,但在前面的示例中无效。


我们解决了第一个问题,但又遇到了第二个问题。 假设我们有一个带有显式默认构造函数的类B ,并且我们想隐式构造pair<int, B>


 struct B { explicit B(); }; pair<int, B> p = {}; 

我们可以做到,但是按照标准,它不可行。 按照标准,只有当两个元素的两个元素都被隐式默认为构造时,才应将其隐式默认为构造。


问题:我们是否需要编写显式对的构造函数? 在C ++ 17中,我们有一个Solomon解决方案:让我们这样写。


 template<typename F, typename S> struct pair { F f; S s; template<typename T = F, typename U = S, typename = enable_if_t<conjunction_v< is_default_constructible<T>, is_default_constructible<U>, is_implicity_default_constructible<T>, is_implicity_default_constructible<U> >>> pair() : f(), s() {} template<...> explicit pair() : f(), s() {} }; 

现在我们有两个默认的构造函数:


  • 当元素隐式默认可构造时,我们将根据SFINAE切断其中之一;
  • 第二种情况则相反。

顺便说一下,要在C ++ 17中实现trait is_implicitly_default_constructible类型,我知道这样的解决方案,但是如果没有SFINAE,我不知道解决方案:


 template<typrname T> true_type test(T, int); template<typrname T> false_type test(int, ...); template<typrname T> using is_implicity_default_constructible = decltype(test<T>({}, 0)); 

如果现在尝试隐式构造pair <int, B> ,则将得到编译错误,如下所示:


 template<..., typename = enable_if_t<conjuction_v< is_default_constructible<T>, is_default_constructible<U>, is_implicity_default_constructible<T>, is_implicity_default_constructible<U> >>> ... pair<int, B> p = {}; ... candidate template ignored: requirement 'conjunction_v< is_default_constructible<int>, is_default_constructible<B>, is_implicity_default_constructible<int>, is_implicity_default_constructible<B> >' was not satisfied [with T=int, U=B] 

在不同的编译器中,此错误将具有不同程度的可理解性。 例如,在这种情况下,Microsoft编译器说:“不可能从空的大括号构造成对<int, B> 。” GCC和Clang会补充说:“我们尝试了这样的构造函数,没有一个出现过,”他们将说出各自的理由。


我们在这里有什么设计师? 复制和移动编译器生成了一些构造函数;有些是我们编写的。 使用复制和移动,一切都很简单:他们期望一个参数,而我们传递零。 对于我们的构造函数,原因是替换是软盘的。


GCC说:“替换失败,试图在enable_if<false>找到类型type -找不到,抱歉。”


Clang认为这种情况是特例。 因此,他很酷的显示了这个错误。 如果在评估第一个参数的enable_if时我们得到的是false ,则他写道不满足特定的要求。


同时,我们自己通过使繁琐的条件enable_if破坏了我们的生活。 我们看到事实证明它是false ,但是我们还不知道为什么。


如果我们以这种方式将enable_if分为四个部分,可以克服这一问题:


 template<..., typename = enable_if_t<is_default_constructible<T>::value>>, typename = enable_if_t<is_default_constructible<U>::value>>, typename = enable_if_t<is_implicity_default_constructible<T>::value>>, typename = enable_if_t<is_implicity_default_constructible<U>::value>> > ... 

现在,当我们尝试隐式构造一个对时,我们会收到一条很好的消息,即is_implicitly_default_constructable候选者不适合,因为不满足特征类型is_implicitly_default_constructable要求:


 pair<int, B> p = {}; // candidate template ignored: requirement 'is_implicity_default_constructible<B>::value' was not satisfied with... 

似乎甚至有一秒钟:如果我们拥有如此出色的编译器,为什么我们需要一个概念?
但是我们回想起,默认情况下使用两个模板函数来实现构造函数,并且每个模板都有六个模板参数。 对于声称强大的语言,这是一种失败。


C ++ 20将如何帮助我们? 首先,通过使用require子句重写此模式来摆脱这些模式。 之前我们在enable_if编写的内容,现在我们在enable_if子句参数中编写的内容:


 template<typename F, typename S> struct pair { F f; S s; pair() requires DefaultConstructible<F> && DefaultConstructible<S> && ImplicitlyDefaultConstructible<F> && ImplicitlyDefaultConstructible<S> : f(), s() {} explicit pair() ... }; 

ImplicitlyDefaultConstructible的概念可以使用这样一个不错的require表达式来实现,其中几乎只使用不同形状的括号:


 template<typename T> concept ImplicitlyDefaultConstructible = requires { [] (T) {} ({}); }; 

T ImplicitlyDefaultConstructible , , T . , , SFINAE.


C++20: (conditional) explicit ( noexcept ). explicit . , explicit .


 template<typename F, typename S> struct pair { F f; S s; explicit(!ImplicityDefaultConstructible<F> || !ImplicityDefaultConstructible<S>) pair() requires DefaultConstructible<F> && DefaultConstructible<S> : f(), s() {} }; 

, . , DefaultConstructible , explicit , explicit .


Optional C++17


Optional . , .


. ? , C++ :


 enum Option<T> { None, Some(t) } 

:


 class Optional<T> { final T value; Optional() {this.value = null; } Optional(T value) {this.value = value; } } 

C++: null , value-?


C++ . initialized storage , , . T , optional T , C++ memory model.


 template<typename T> class optional { bool initialized; aligned_storage_t<sizeof(T), alignof(T)> storage; ... 

, . : optional , optional . :


  ... T & get() & { return reinterpret_cast<T &>(storage); } T const & get() const & { return reinterpret_cast<T const &>(storage); } T && get() && { return move(get()); } optional() noexcept : initialized(false) {} optional(T const & value) noexcept(NothrowCopyConstructible<T>) : initialized(true) { new (&storage) T(value); } ~optional() : noexcept(NothrowDestructible<T>) { if (initialized) get().~T(); } }; 

optional ' . optional , optional , , optional . , copy move .


. : assignment . , . . copy constructor. :


 template<typename T> class optional { bool initialized; aligned_storage_t<sizeof(T), alignof(T)> storage; ... optional(optional const & other) noexcept(NothrowCopyConstructible<T>) : initialized(other.initialized) { if (initialized) new (&storage) T(other.get()); } optional& operator =(optional && other) noexcept(...) {...} }; 

move assignment. , :


  • optional ' , .
  • , .
  • , — , , .

T : move constructor, move assignment :


 optional& operator =(optional && other) noexcept(...) { if (initialized) { if (other.initialized) { get() = move(other.get()); } else { initialized = false; other.initilized = true; new(&other.storage) T(move(get())); get().~T(); } } else if (other.initialized) { initialized = true; other.initialized = false; new(&storage) T(move(get())); other.get().~T(); } return *this; } 

noexcept :


 optional& operator =(optional && other) noexcept(NothrowAssignable<T> && NothrowMoveConstructible<T> && NothrowDestructible<T>) { if (initialized) { if (other.initialized) { get() = move(other.get()); } else { initialized = false; other.initialized = true; new (&other.storage) T(move(get())); get().~T(); } } ... } 

optional :


 template<typename T> class optional { ... optional(optional const &) noexcept(NothrowCopyConstructible<T>); optional(optional &&) noexcept(NothrowMoveConstructible<T>); optional& operator =(optional const &) noexcept(...); optional& operator =(optional &&) noexcept(...); }; 

, pair :
Optional -, (, deleted), compilation error.


 template class optional<unique_ptr<int>>; // compilation error 

, optional unique_ptr ,
copy constructor copy assignment deleted. , , SFINAE.
copy move assignment , — . - , copy , .


— . copy : deleted operation , , operation:


  • deleted_copy_construct delete , — default ;
  • copy_construct , copy_construct .

 template<class Base> struct deleted_copy_construct : Base { deleted_copy_construct(deleted_copy_construct const &) = delete; deleted_copy_construct(deleted_copy_construct &&) = default; deleted_copy_construct& operator =(deleted_copy_construct const &) = default; deleted_copy_construct& operator =(deleted_copy_construct &&) = default; }; template<class Base> struct copy_construct : Base { copy_construct(copy_construct const & other) noexcept(noexcept(Base::construct(other))) { Base::construct(other); } copy_construct(copy_construct &&) = default; copy_construct& operator =(copy_construct const &) = default; copy_construct& operator =(copy_construct &&) = default; }; 

select_copy_construct , , CopyConstrictuble , copy_construct , deleted_copy_construct :


 template<typename T, class Base> using select_copy_construct = conditional_t<CopyConstructible<T> copy_construct<Base> deleted_copy_construct<Base> >; 

, optional , optional_base , copy construct , optional
select_copy_construct<T, optional_base<T>> . copy :


 template<typename T> class optional_base { ... void construct(optional_base const & other) noexcept(NothrowCopyConstructible<T>) { if ((initialized = other.initialized)) new (&storage) t(other.get()); } }; template<typename T> class optional : select_copy_construct<T, optional_base<T>> { ... }; 

. , , copy_construct , move_construct copy_construct , copy_assign , , move_construct , , , :


 template<typename T, class Base> using select_move_construct = select_copy_construct<T, conditional_t<MoveConstructible<T>, move_construct<Base> > >; template<typename T, class Base> using select_copy_assign = select_move_construct<T, conditional_t<CopyAssignable<T> && CopyConstructible<T>, copy_assign<Base> delete_copy_assign<Base> > >; 

, move_assign copy_assign , optional_base , assignment construct assign , optional select_move_assign<T, optional_base<T>> .


 template<typename T, class Base> using select_move_assign = select_copy_assign<T, ...>; template<typename T> class optional_base { ... void construct(optional_base const&) noexcept(NothrowCopyConstructible<T>); void construct(optional_base &&) noexcept(NothrowMoveConstructible<T>); optional_base& assign(optional_base &&) noexcept(...); optional_base& assign(optional_base const &) noexcept(...); }; template<typename T> class optional : select_move_assign<T, optional_base<T>> { ... }; 

, :
optional<unique_ptr> deleted_copy_construct ,
move_construct . !


 optional<unique_ptr<int>> : deleted_copy_construct<...> : move_construct<...> : deleted_copy_assign<...> : move_assign<...> : optional_base<unique_ptr<int>> 

: optional TriviallyCopyable TriviallyCopyable .


TriviallyCopyable ? , T TriviallyCopyable ,
memcpy . , .


, , , . resize vector TriviallyCopyable , memcpy , , . , , .


TriviallyCopyable , , static_assert ', copy-move :


 template<typename T> class optional : select_move_assign<T, optional_base<T>> {...}; static_assert(TriviallyCopyable<optional<int>>); static_assert(TriviallyCopyConstructible<optional<int>>); static_assert(TriviallyMoveConstructible<optional<int>>); static_assert(TriviallyCopyAssignable <optional<int>>); static_assert(TriviallyMoveAssignable <optional<int>>); static_assert(TriviallyDestructible <optional<int>>); 

static_assert ' . , , . optionalaligned_storage , , , , TriviallyCopyable .


, . , TriviallyCopyable .


, . select_copy_construct :


 template<typename T, class Base> using select_copy_construct = conditional_t<CopyConstructible<T>, copy_construct<Base> deleted_copy_construct<Base> >; 

CopyContructible copy_construct , if compile-time: CopyContructible TriviallyCopyContructible , Base .


 template<typename T, class Base> using select_copy_construct = conditional_t<CopyConstructible<T>, conditional_t<TriviallyCopyConstructible<T>, Base, copy_construct<Base> >, deleted_copy_construct<Base> >; 

, copy . , select_destruct . int , - - , .


 template<typename T, class Base> using select_destruct = conditional_t<TriviallyDenstructible<T>, Base, destruct<Base> > >; 

, , . , , :


 optional<unique_ptr<int>> : deleted_copy_construct<...> : move_construct<...> : deleted_copy_assign<...> : move_assign<...> : destruct<optional_base<unique_ptr<int>>> : optional_base<unique_ptr<int>> 

, C++17 optional 7; : operation , deleted_operation select_operation ; construct assign . , .


- . . : deleted.


, noexcept .
, , , trivial , noexcept . , , trivial noexcept , noexcept , deleted . . , , .


type trait, , . , , copy : deleted , nothrow , ?


, - special member, , , , :


  • , deleted , = delete deleted_copy_construct ;
  • , copy_construct , c noexcept ;
  • , , , .

.


optional C++20


C++20 optional copy ?
:


  • T CopyConstructible , deleted ;
  • TriviallyCopyConstructible , ;
  • noexcept .

 template<typename T> class optional { ... optional(optional const &) requires(!CopyConstructible<T>) = delete; // #1 optional(optional const &) requires(TriviallyCopyConstructible<T>) = default; // #2 optional(optional const &) noexcept(NothrowCopyConstructible<T>) {...} // #3 ... ~optional() requires(TriviallyDestructible<T>) = default; ~optional() noexcept(NothroeDestructible<T>) {...} }; 

, . -, , T requires clause false . requires(false) , , overload resolution. , requires(true) , .
, .


requires clause = delete :


  • = delete overload resolution, , , deleted .
  • requires(false) overload resolution.

, copy , , requires clause. .


, . ! C++ , ? , , . , , , . , , , , , optional .


, , GCC internal compiler error, Clang . , . , .


, , optional C++20. , , C++17.


aligned_storage aligned_union


: aligned_storage reinterpret_cast , reinterpret_cast constexpr . , compile-time optional , compile-time. STL aligned_storage optional aligned_union variant . , , STL Boost optional variant . variant , :


 template<bool all_types_are_trivially_destructible, typename...> class _Variant_storage_; template<typename... _Types> using _Variant_storage = _Variant_storage_< conjunction_v<is_trivially_destructible<_Types>...>, _Types... >; template<typename _First, typename... _Rest> class _Variant_storage_<true, _First, _Rest...> { union { remove_const_t<First> _Head; _Variant_storage<_Rest...> _Tail; }; }; 

variant . _Variant_storage_ , , -, , variant , -, . , trivially_destructible ? type alias, . _Variant_storage_ , true false . , true , . trivially_destructible , union Variant ' .


, , , , . type alias _Variant_storage . :


 template<typename... _Types, bool = conjunction_v<is_trivially_destructible<_Types>...> > class _Variant_storage_; 

. , variadic template . , , , _Types . C++17 , .


C++20 ,
,
requires clause. C++20 requires clause:


 template<typename... _Types> class _Variant_storage_; template<typename _First, typename... _Rest> requires(TriviallyDestructible<_First> && ... && TriviallyDestuctible<_Rest>) class _Variant_storage_<_First, _Rest...> { union { remove_const_t<_First> _Head; _Variant_storage_<_Rest...> _Tail }; }; 

_Variant_storage_ , TriviallyDestructible . , requires clause , , .


requires clause template type alias


, requires clause template type alias. C++20 - enable_if , :


 template<bool condition, typename T = void> requires condition using enable_if_t = T; 

,


, . :


 // Equivalent, but functionally not equivalent template<typename T> enable_if_t<(sizeof(T) < 239)> f(); template<typename T> enable_if_t<(sizeof(T) > 239)> f(); // Not equivalent template<typename T> requires(sizeof(T) < 239) void f(); template<typename T> requires(sizeof(T) > 239) void f(); 

, enable_if . ? f() : enable_if , , 239, , , , 239. , :


  • , , template type alias', «void f(); void f();
  • , SFINAE, , , .

, enable_if , , size < 239 , size > 239 . , . , f() . requires clause. — , .


— , . C++ Russia 2019 Piter, «: core language» . , , : reachable entity visible, ADL, entities internal linkage . , C++ Russia (JetBrains) « ++20 — ?»

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


All Articles