推断这一点

2021-03-18 17:50:53

我们提出了一种新机制,用于指定或致电成员函数被调用的表达式的值类别。换句话说,从成员内函数函数的方式函数是否载于它的表达式是偏僻的或rvalue;无论是常数还是挥发性;和表达式的类型。

重新添加的部分与我们考虑的其他语法的历史(用于后面)以及对反射,显式静态,虚拟和金属版的讨论。在戴维斯纸上重新撰写的措辞。重新触及以便显式对象成员函数是非静态成员函数而不是静态成员函数。

贝尔法斯特在ewg的反馈是“这看起来不错,回到措辞和实施”。此版本添加措辞,实现在于作品中。

计算扣除的常见问题解答条目,EWGI在科纳要求的正交功能。

[P0847R1]于2018年11月在San Diego提供了各种语法和名称查找选项。讨论揭示了对Lambdas的一些潜在问题,需要熨烫。此修订Zeroes在一个特定语法和名称查找语义上,解决了所有使用情况。

[P0847R0]在2018年6月在rapperswil中介绍了使用该自我和amp中使用的那篇文章中使用的语法。自我表明显式对象参数而不是自我和amp;&这种出现在纸张R0中的自我。

不同的语法,将对象参数的类型的类型放置在成员函数的参数声明(CV-Ref Pretifiers今天)之后

不同的名称查找方案,可以防止来自具有显式自型注释的新型成员函数中的隐式/不合格访问,无论语法如何。

此修订仔细探讨了这两个方向,呈现了不同的语法和查找方案,并在深度多用例中讨论以及每个语法如何或无法解决它们。

在C ++ 03中,成员函数可以具有CV-restification,因此可以具有特定类想要特定成员的Const和非Const重载的场景。 (请注意,也可以希望易失性过载,但是那些不太常见,因此在此处没有检查。)在这些情况下,两个过载都执行相同的事情 - 唯一的区别在于被访问和使用的类型。这是通过重复函数来处理,同时根据需要调整类型和资格,或者将一个过载委托给另一个重载委托。后者的一个例子可以在Scott Meyers的“有效的C ++”[Effcpp]中找到,第3项:

Class TextBlock {Public:Char Const&操作员[](size_t位置)const {// ...返回文本[位置]; } char&运算符[](size_t位置){return const_cast< char&>(static_cast< textblock const&(*这个)[位置]); } // ......};

在C ++ 11中,成员函数获取了新轴来专门执行:Ref-Pretifiers。现在,而不是潜在地需要两个成员函数的过载,而不是四个:&,const&&或const&&amp ;.我们有三种方法来处理这一点:

我们所有四个重载都以私有静态成员函数的形式委托给助手。

模板< typename t> class可选{// ... constexpr t&价值()& {if(has_value()){返回它 - > m_value; }掷bad_optional_access(); } constexpr t const&价值()const& {if(has_value()){返回它 - > m_value; }掷bad_optional_access(); } constexpr t&&价值()&& {if(has_value()){返回移动(这个 - > m_value); }掷bad_optional_access(); } Constexpr t const&&价值()const&& {if(has_value()){返回移动(这个 - > m_value); }掷bad_optional_access(); } // ......};

模板< typename t> class可选{// ... constexpr t&价值()& {return const_cast< t>(static_cast< oleasting const&(*这个).value()); } constexpr t const&价值()const& {if(has_value()){返回它 - > m_value; }掷bad_optional_access(); } constexpr t&&价值()&& {返回const_cast< t&&(static_cast<>>(*这个).value()); } Constexpr t const&&价值()const&& {return static_cast< t const&>(value()); } // ......};

模板< typename t> class可选{// ... constexpr t&价值()& {return value_impl(*这); } constexpr t const&价值()const& {return value_impl(*这); } constexpr t&&价值()&& {return value_impl(移动(*此)); } Constexpr t const&&价值()const&& {return value_impl(移动(*此));私人:模板< typename opt>静态Declitype(Auto)value_impl(OPT&& opt){if(!opt .has_value()){opt bad_optional_access();返回前进<选择>(选择).m_value; } // ......};

这远非复杂的功能,但基本上重复相同的代码四次 - 或者使用人工代表团避免这样做 - 乞求重写。不幸的是,不可能改善;我们必须以这种方式实现它。似乎我们应该能够向非成员职能摘要摘要限定员,在那里我们根本没有这个问题:

模板< typename t> class可选{// ...模板< typename opt>朋友Decltype(Auto)值(OPT& o){if(o .has_value()){返回前进< opt>(o).m_value; }掷bad_optional_access(); } // ......};

所有四个案例都以一个函数处理......除了它是非成员函数,而不是成员函数。不同的语义,语法不同,没有帮助。

我们需要许多情况下我们需要两个或四个相同的成员函数的过载,用于不同的const - 或ref-pretifiers。不仅如此,可能有额外的情况,其中一个类应该有四个特定成员函数的过载,但由于开发人员懒惰,没有。我们认为有足够的这种情况来优于更好的解决方案,而不是简单地“写它,再次写作,然后再写两次。”

我们提出了一种新的方式来声明非静态成员函数,该函数将允许在仍然具有常规成员函数语法中仍可接受的同时进行类实例参数的类型和值类别。这是对语言的严格扩展。

我们认为,在没有重复的情况下写入CV-REF限定符感知的成员函数模板的能力将提高代码可维护性,降低错误的可能性,并快速制作,更正的代码更易于写入。

该提案足够一般,正交,以允许用于C ++的几个新令人兴奋的功能和设计模式:

该提案假定存在两个图书馆的添加,但它不提出它们:

like_t,将第一类型的cv和ref-letifiers应用到第二个(例如lick_t< int&,double>是双倍&,< x const&&,x const&&是y const&&等)

forward_like,forward的版本旨在转发不基于其自己的类型的变量,而是基于其他类型的类型。 Forward_like< t>(u)是前进的短手< late_t< d,decltype(u)>>(u)。

可以声明非静态成员函数作为其第一个参数进行一个显式对象参数,用前缀关键字表示。一旦我们将对象参数提升到适当的功能参数,就可以在正常函数模板扣除规则下推导出来:

struct x {void foo(这个x const& self,int i);模板<类型自我和gt; void bar(这个自我和amp; self); }; struct d:x {}; void ex(x& x,d const& d){x .foo(42); //#39;自我'符合' x&#39 ;,' i'是42 x .bar(); //将Self授予x&,呼叫x :: bar< x>>移动(x).bar(); //将Self授予X,呼叫X :: Bar< x> d .foo(17); //#39;自我'必将' d' d .bar(); //将Self as d const&,呼叫x :: bar< d const&> }

具有显式对象参数的成员函数不能是静态的或虚拟的,并且它们不能具有CV-或REF - 限定符。我们将讨论跟踪部分中的静态和虚拟的限制。

对成员函数的调用将将对象参数解释为它的第一个(本 - 注释)参数;然后将括号表达式列表中的第一个参数解释为第二个参数等。

在正常扣除规则之后,对应于显式对象参数的模板参数可以推导到从声明成员函数被声明的类的类型,如上面的d .bar())。

我们可以使用此语法来实现可选的:: value()和可选::运算符 - >()只有两个函数而不是当前六:

模板< typename t>结构可选{模板<类型自我和gt; Constexpr Auto&&价值(这个自我和amp;& self){if(!self .has_value()){oppl bad_optional_access();返回前进< self>(self).m_value;模板<类型自我和gt; Constexpr自动运营商 - >(这个自我和amp; self){返回地址(self .m_value); }};

此语法也可以在Lambdas中使用,并且此注释参数揭示了在其身体中引用Lambda本身的方式:

矢量捕获= {1,2,3,4}; [捕获](此汽车&自我) - > Decltype(Auto){返回转发_like< DECLTYPE(自我)>(抓获); [捕获]<阶级自我>(这个自我和amp;自我) - > Decltype(Auto){返回转发< self>(捕获); }

Lambdas可以从捕获中移动或复制,具体取决于Lambda是否是偏航或rvalue。

以下是描述如何挖掘这一切影响所有重要语言构造 - 名称查找,键入扣除,过载解决方案等。

在C ++ 17中,名称查找包括在调用名称函数或呼叫运算符,在类类型的对象上时,常规类查找中发现的静态和非静态成员函数。非静态成员函数被视为有一个隐式对象参数,其类型是LVALUE或RVALUE参考对CV x(其中参考和CV限定符是基于函数自己的限定符确定的,它绑定到其上的对象函数被调用。

对于使用显式对象参数的非静态成员函数,查找将与C ++ 17中的其他成员函数相同的方式,其中一个例外:而不是基于CV和Ref隐含地确定对象参数的类型成员函数的限定符,现在通过提供的显式对象参数的类型明确确定。以下示例说明了这一概念。

struct x {//隐式对象具有类型x& Void foo()&amp ;; //隐式对象具有类型x const& Void foo()const&amp ;; //隐式对象具有类型x&& Void Bar()&&amp ;; };

struct x {// explicit对象具有类型x& void foo(这个x&); //显式对象具有X Const& void foo(这个x const&); //显式对象有类型x&&禁止栏(此x&); };

在C ++ 17中的表达式上的表达式上的名称查找将在第一列中找到FOO的两个过载,因此丢弃的非Const过载应该是Const。

使用所提出的语法,obj .foo()将继续找到foo的过载,具有相同的行为到C ++ 17。

我们查找候选函数的唯一变更是在一个显式对象参数的情况下,参数列表由一个转移。第一个列出的参数绑定到对象参数,第二个列出的参数对应于调用表达式的第一个参数。

本文并未提出过载解决方案的任何更改,但仅建议将候选集扩展为包括以新语法编写的非静态成员函数和成员函数模板。因此,鉴于呼叫x .foo(),如果x不是const,如果x不是const,则过载解析仍将选择第一个foo()过载。

唯一候选人的唯一变革涉及,该提案允许扣除对象参数,这是语言的新功能。

由于在某些情况下有多种方式来声明相同的函数,因此它将不成本,以声明具有相同参数的两个函数和对象参数相同的限定符。这是:

struct x {void bar()&amp ;;禁止栏(此x&); //错误:相同的此参数类型静态void f(); void f(这个x const&); //错误:两个函数没有参数};

此提议的主要动机之一是推断为类对象的CV - 限定符和价值类别,这要求从调用成员函数的对象中缝合显式成员对象或类型。

如果对象参数的类型是模板参数,则所有通常的模板扣除规则都适用于预期:

结构x {模板<类型自我和gt;空白foo(这个自我和amp;&,int); }; struct d:x {}; void ex(x& x,d& d){x .foo(1); // self = x&移动(x).foo(2); // self = x d .foo(3); // self = d& }

重要的是要强调扣除能够推断出一种极其强大的衍生类型。在最后一行中,无论语法如何,都将作为D&这对成员函数中的名称查找有影响,并导致潜在的模板参数推导扩展。

但如果显式类型没有参考类型,该怎么办?这应该是什么意思?

struct less_than {模板< typename t,typename u> BOOL运算符()(此LIST_THAN,T CONST& LHS,U CONTER& RHS){返回LHS< RHS; }}; less_than {}(4,5);

显然,参数规范不应划分,第一个参数(less_than {})通过值传递。

在候选查找的提议规则之后,呼叫运算符在这里是候选者,对象参数绑定到(空)对象和其他两个参数绑定到参数。具有一个值参数,所有语言都不是新的 - 它具有清晰明显的含义,但我们从未能够以前的价值拍摄对象参数。对于这可能是可取的,请参阅副价率成员函数。

到目前为止,我们只考虑了使用名称查找以及它们如何推断该参数的成员函数如何找到具有显式对象参数的成员函数。现在我们继续如何如何表现出这些职能的机构。

由于从调用函数的对象推断出显式对象参数,因此这具有挖掘派生类型的可能效果。我们必须仔细考虑在此上下文中如何查找名称。

struct b {int i = 0;模板<类型自我和gt;自动&& F1(这个自我和amp;){返回我;模板<类型自我和gt;自动&& F2(这个自我和amp;&){返回这个 - >我;模板<类型自我和gt;自动&& F3(这个自我&&){返回forward_like< self>(*这).i;模板<类型自我和gt;自动&& F4(这个自我和amp;&){返回前进< self>(*这).i;模板<类型自我和gt;自动&& F5(这个自我和amp;& self){返回前进< self>(self).i; }}; struct d:b {//阴影b :::我加倍i = 3.14; };

问题是,这五个职能中的每一个有什么作用?他们应该是不均成的吗?什么是最安全的选择?

如果有一个显式对象参数,则无法访问,每个访问必须通过自我。通过此内容没有隐性查找。这使得F1通过F4不良而且仅形成F5良好的F5。但是,虽然b().f5()返回对b :: i的引用,而d().f5()返回对d :: i的引用,因为自我是对D的引用。

如果有一个显式对象参数,可以访问它,并指向基本子object。成员没有隐性查询;所有访问必须通过此或自我明确。这使得F1形成不良。 F2将是良好的,始终回到B :: i的参考。最重要的是,如果推断出显式对象参数,这将是依赖的。这个 - >我总是是一个int,但它可以是int或int const const,具体取决于b对象是const的。 F3将始终是良好的,并且是返回B :: i的转发参考的正确方法。如果由于所请求的隐式衰落,则在B上调用而单独的B但是不成集的F4将是良好的。如前所述,F5将是良好的。

始终可以访问,并指向基本子object;我们允许隐式查找为C ++ 17。这与前一个选择相同,除了现在F1是良好的,并且与F2完全相同。

在San Diego讨论之后,我们提出的选项是#1。这允许最清晰的模型是这种注释功能的:它是一个静态成员函数,提供更方便的功能调用语法。此类功能中没有隐含的这一点,唯一提到这将是对象参数的注释。必须通过对象参数直接完成所有成员访问。

这种选择的结果是我们需要防止被推导到派生类型的对象参数。要确保上面的F5()始终返回对B :: I的引用,我们需要编写以下其中一个:

模板<类型自我和gt;自动&& F5(这种自我和amp;& self){//明确地向自我施加到适当的b //注意,我们必须投入自我,而不是自我.i返回static_cast< lime_t< self,b>&&& ;>(self).i; //使用显式子object语法。请注意,这始终是//偏僻的参考 - 不是转发参考返回自我.b :: i; //使用显式子object语法获取转发参考返回前进返回< self>(self).b :: i; }

此提议的最坏情况是我们不打算致力于授予派生对象的情况 - 我们只意味着推断资格级别 - 但是从我们私下继承的派生类型是我们的成员之一 -

B级{int i;公共:模板<类型自我和gt;自动&&得到(这个Self&& self){//查看了上面的:我们需要减轻阴影返回转发< self>(self).b :: i; }}; D级:私人B {双我;公众:使用B :: GET; }; d().get(); // 错误

在这个例子中,自我推断为d(不是b),但我们选择的阴影缓解将不起作用 - 我们可以

......