Clang中C++的UFCS

2020-08-04 02:45:57

TLDR;统一函数调用语法(UFC)是有用和优雅的。我在CLANG中实现了一个类似于C#的“扩展方法”的UFC变体,您可以在https://github.com/dancrn/llvm-project.上查看。

关于C++中的UFC的建议一直是一个长期的讨论(N1585,N4165,N4174,N4474,P0079R0),许多人似乎都在积极讨论,包括Herb Sutter和Bjarne Stroustrup。C#将UFC称为扩展方法,我个人发现它们非常有用。如果您不熟悉C#的扩展方法,它们如下所示:

公共静态类扩展{公共静态字符串ValueOrDefault(此字符串输入,字符串defaultValue){返回字符串。IsNullOrWhiteSpace(输入)开关{true=>;defaultValue,false=>;input};}}公共字符串GetValue(String Str){返回字符串。ValueOrDefault(";未提供值";);}。

调用扩展方法时,它们看起来像定义它们的类型上的第一类方法。提供的示例与现有的自由格式调用(即Extensions.GetValue(What,";Nothing";))相比没有太多好处-这也是调用该方法的有效方式。但是,当静态扩展方法被视为扩展现有的不可修改类的泛型方法时,它们就会发挥作用。LanguageExt就是这样一个C#库,它提供了对基本IEnumerable接口的函数扩展(以及其他许多东西),尽管还有很多其他示例扩展了其他常用的库。

不幸的是,虽然提案偶尔会重新浮出水面,但统一呼叫语法的活动似乎停滞不前。我想看看需要什么才能实现它,谁知道,如果有足够多的人喜欢和使用UFC,它可能会变成..。也许是C++30?

Revzin有几篇优秀的文章,更全面地描述了UFC。本质上,UFC可以分为两类行为:“候选集”功能描述针对特定调用风格考虑哪些函数,以及“重载解析”方法描述当有多个候选者时如何确定应该选择哪个成员或函数。在不重复这些描述的情况下,该模型可以被认为是CS4-添加语法以指示UFC候选资格-和OR2-对所有候选执行正常的重载解析。我不会花太多时间来解释我为什么做出这些选择,但我会简短地说:

虽然严格来说不是“最纯粹”的决定,但我认为允许用户指定他们打算在重载解析中使用哪些函数是明智的。而且,虽然严格地说不是优先级,但从编译时间的角度来看,将候选集合保持在尽可能小的范围内将是有益的。它还允许UFC向后兼容现有代码,这意味着我认为这是最明智的做法。

有两种明显的方式可以使用this关键字来指示UFC候选,作为参数限定符,或者作为参数名-对于任何熟悉C#的人来说,很明显,CS4和this参数限定符是在实现其UFC概念时选择的样式。语法添加有很多明智的选择,但我考虑了两个。它们看起来都类似于:

//1.';this';参数名int func(const std::string&;this);//2.';this';this';限定符int func(this const std::string&;param);

隐式此值通常可以访问受私有&;保护的类成员,即UFC函数不能访问的成员。

选择参数类型是为了说明不一致性:当在成员函数中使用时,它通常被认为是一个指针,也就是说,我们使用的是这个->;值,而不是这个.value。我们应该接受什么样的UFCS功能?

UFCS候选资格最容易被视为是函数的属性,而不是参数-为什么要更改参数?

这是一个通常表示值的关键字,虽然C++在-auto之前改变了关键字的用途,但这可能不是我们所希望的。

最后,第一个选项似乎提出的问题多于它回答的问题,所以我选择了第二个选项。

我认为对于UFC来说,最糟糕的情况是类的成员函数发生更改,在与该类型的值交互的代码的另一部分中屏蔽UFC调用。例如,考虑以下情况:

//从include<;ome/library ary.h>;中,//上下文使用从文件";读取的单个";函数定义。类context{public:int read_from_fd(Int Fd);};//在消费代码中,定义了以下扩展名int read_from_file(this context&;ctx,file*fp);

现在,库已更新到更高版本,该版本包括其自己的read_from_file方法:

//from include<;ome/library ary.h>;,class context{public:int read_from_fd(Int Fd);int read_from_file(file*fp);};//该函数只能配合常规函数调用语法int read_from_file(this context&;ctx,file*fp)使用;

在这种情况下,首选成员调用而不是UFC调用会悄悄地更改此代码的行为,而不会对read_from_file方法进行任何明显的更改。一般说来,我认为偏爱一种类型的调用而不是另一种类型的调用是不明智的,因此这似乎排除了任何形式的重载解决方案,即优先于一种类型的调用而不是另一种类型的调用。因此OR1和OR2+似乎不是最好的方法,而选择CS4则排除了OR3作为选项。在我的实现中,调用之间的任何歧义都被视为错误,就像现在一样。

应该注意的是,OR2的选择与C#的扩展方法相反,在UFC候选和成员函数之间存在歧义的情况下,总是选择成员函数(即,C#使用OR2+)。

这在声明说明符(const、Volatile等)之前。文件/命名空间范围内函数的第一个参数的。

不能将类方法定义为UFC候选方法(尽管对于非实例方法可能会放宽这一点)。

X.f(Y)形式的调用除了执行成员查找之外,还执行名称为f的函数的名称查找,并使用参数x和y重载解析。

重载解析照常进行,即,如果候选集包含类方法和UFCS候选者,则对其中任何一个都没有优惠待遇,这是错误的。

总而言之,我们将能够定义看起来像是在类上定义的方法的函数,如下所示:

Class foo{private:std::string m_bar;public:foo(const std::string&;bar):m_bar(Bar){}std::string get_bar()const noExcept{return m_bar;}};int get_bar_length(this const foo&;val){return val。Get_bar()。长度();}。

Void F1(){auto val=foo(";意大利面&34;);//这两个调用在语义上是相同的assert(val.。Get_bar_length()==get_bar_length(Val));}。

我不会在这里介绍Clang-我希望任何读者都会熟悉它。我之所以从朗开始,而不是从GCC开始,主要是因为萨尔拉兹关于支持朗实现概念的故事。无论如何,Clang似乎是一个合适的实施基础:

我在今年4月左右“真正”开始了UFCS,尽管我可能从2019年9月开始断断续续地阅读和思考它。总的来说,我认为Clang的代码质量不错,虽然学习曲线可能是我遇到过的最陡峭的,但让我印象深刻的是,你不需要完全理解就能让一些东西工作-代码真的是非常模块化的。也就是说,让一些东西正常工作,而不是让一些完整的东西工作,需要了解非常大的代码区域。解析C++是很多人都会认为是异国情调的事情,因此一个地方的微小更改可能会在您意想不到的地方产生影响。

现在,我有一个可以通过make clang-test中所有测试的工作实现。当然,还添加了‘Parse’和‘SemaCXX’测试,分别是cxx-ufcs.cpp和Unified-call-syntax.cpp。我已经添加了适当的附加诊断消息(尽管是作为解析器错误,而不是语义分析错误),不过我还想添加其他一些消息。我已经在几个项目上测试了我的自定义版本的Clang,它似乎也能像预期的那样工作。总体而言,我对结果相当满意,我(天真地)希望除了我自己之外的其他人能试一试:)。

如果您想尝试UFC,那么您可以从这里签出并构建Clang,没有其他需要配置的东西(尽管我建议您不要在默认前缀中安装它!)。要启用UFC,您需要在调用Clang时向其传递一个额外的参数-fufcs。前端驱动程序根本没有更改,因此很可能需要手动将其传递给编译器:

同样,考虑到此实现的设计,编译现有代码应该不会有任何问题。如果这不是案例,那么可以在GitHub上随意创建问题!

虽然我对我的更改按预期工作有一定的信心,但我不认为这是“完成的”。有几件事让人感觉不太对劲,而且,即使这永远不会被合并到clang(这可能有点希望……),我也想做“正确的”。

虽然Clang对支持UFC的更改不是很多,但它的测试仍然比我希望的要少。我绝对不建议在任何形式的产品代码中使用此代码:)。

我的部分更改在这个位字段中添加了另一个位,它指定函数声明是否为UFC候选。这是不希望的,因为它会将一些其他依赖类型推入超过其8字节限制的1位。在阅读了为什么会这样做之后,似乎这不是我想要坚持的东西。相反,我认为创建从FunctionDecl派生的新函数声明类型可能是更好的方法。

为UFC候选提出的语法更改意味着需要更改解析器以支持它。也就是说,解析器正在执行一些检查,我认为这些检查可能更适合在语义分析期间完成。

应该允许显式命名空间限定UFC候选。我目前完全没有实现这一点。它将允许使用如下用途:

命名空间ext{int get_bar(this const foo&;x){//...}}int func(){return foo(";x";)。Ext::get_bar();}。

Clang的一大特点是它的工程师们在生成有用的错误消息方面不遗余力。我认为有一点欠缺的是警告:按照现在的情况,您可以编写一个UFC候选函数,该函数将屏蔽(并因此使其不明确)一个成员函数。我认为这样做不应该是错误的(也许应该是错误的?),但如果是这样的话,至少发出一个警告会很好:

Class foo{int bar();//注意:此处定义};int bar(this foo&;f);//警告:UFCS候选不会掩蔽类方法';foo::bar()';

毫无疑问,C++是我最喜欢的语言,进入一个非常流行的编译器实现的代码,并修改它以自己的方式扩展它,既有趣又具有挑战性。此外,我对解析C++有了新的认识:)如果有人想检查GitHub上的代码,可以在这里找到,我将非常高兴任何人得到的任何反馈,以及关于我如何改进它的评论或建议。