通用引用的语法
Posted
技术标签:
【中文标题】通用引用的语法【英文标题】:Syntax for universal references 【发布时间】:2021-10-12 20:36:25 【问题描述】:这是一个右值引用:
void foo(int&& a);
它不绑定到左值:
int i = 42;
foo(i); // error
这是一个通用参考:
template<typename T>
void bar(T&& b);
它绑定到右值,也绑定到左值:
bar(i); // okay
这是一个右值引用:
template<typename T>
struct X
void baz(T&& c);
;
它不绑定到左值:
X<int> x;
x.baz(i); // error
为什么通用引用使用与右值引用相同的语法?这不是不必要的混乱来源吗?委员会有没有考虑过像T&&&
、T&*
、T@
或T&42
这样的替代语法(开个玩笑)?如果是这样,拒绝替代语法的原因是什么?
【问题讨论】:
“通用参考”不是标准中的术语。它们都是 r-value refs,有时会出现 ref 折叠规则。所以没有语法,因为标准中“没有通用引用之类的东西”。 @Mat 对,但是参考折叠规则属于我所质疑的设计 :) 这不像参考折叠规则是上帝赐予的,并且必须围绕它们设计右值引用:) 有用的链接 T&& Doesn’t Always Mean “Rvalue Reference” 来自 Scott Meyers @KerrekSB,并非如此,必须更改规则以允许T&&
将 T
推断为引用类型,它不只是出现。
X x;
错过了模板参数。使用X<int&> x;
时代码应该可以编译。
【参考方案1】:
诸如T&&
之类的通用引用可以将T
推断为“对象类型”或“引用类型”
在您的示例中,它可以在传递右值时将T
推断为int
,因此函数参数为int&&
,或者在传递左值时它可以将T
推断为int&
,在这种情况下函数参数是int&
(因为引用折叠规则说std::add_rvalue_reference<int&>::type
只是int&
)
如果T
不是由函数调用推导出来的(如在您的X::baz
示例中),则它不能推导出为int&
,因此该引用不是通用引用。
所以恕我直言,真的不需要新的语法,它非常适合模板参数推导和引用折叠规则,只需稍加调整即可将模板参数推导出为引用类型(在 C++03 中为函数模板T
或 T&
类型的参数将始终将 T
推断为对象类型。)
这些语义和语法是从一开始就提出的,当时提出了右值引用和对参数推导规则的调整作为转发问题的解决方案,请参阅N1385。使用这种语法来提供完美转发的提议与出于移动语义的目的提议右值引用并行:N1377 与 N1385 在同一个邮件中。我认为从未认真提出过替代语法。
恕我直言,无论如何,另一种语法实际上会更加混乱。如果您将template<typename T> void bar(T&@)
作为通用引用的语法,但与我们今天的语义相同,那么在调用bar(i)
时,模板参数T
可以推导出为int&
或int
和函数参数将是int&
或int&&
类型...两者都不是“T&@
”(无论该类型是什么。)所以你会有声明符T&@
的语言语法,这不是一个类型可能永远存在,因为它实际上总是引用其他类型,int&
或 int&&
。
至少在语法上,T&&
是一个真正的类型,并且引用折叠规则并不特定于使用通用引用的函数模板,它们与外部类型系统的其余部分完全一致模板数:
struct A a;
typedef A& T;
T&& ref = a; // T&& == A&
或等效:
struct A a;
typedef A& T;
std::add_rvalue_reference<T>::type ref = a; // type == A&
当T
是左值引用类型时,T&&
也是。我认为不需要新的语法,规则真的没有那么复杂或混乱。
【讨论】:
+1 解释得很好。斯科特在使用他发明的术语通用参考之前联系了我,我担心这实际上可能会导致比教授标准实际内容更多的混乱。尽管如此,Scott 在成为一名优秀的 C++ 教师方面有着出色的记录。也许使用他的术语会帮助人们学习。或者它只会引起混乱。老实说,我不知道。无论如何,乔纳森对事物为何如此的解释以及对论文的引用都是正确的。 @HowardHinnant,同样在这里,我一直计划为 ACCU 期刊 Overload 写一篇文章,根据上面的typedef
和 add_rvalue_reference
示例进行解释,但斯科特首先发表。在为 Overload 审查他的作品时,我有完全相同的保留意见,但我决定因为他是一个比我好得多的老师(拥有更广泛的读者群!)我相信他会做得更好向世界解释它。我现在在公共场合使用通用引用这个词,但我仍然认为是推导和引用折叠。
嗯,只是注意到 Daveed 对我温和的 rage on cpp-next.com 的回答是 “我(和其他人)表达了我的偏好,即不尝试将所有内容都放在右值引用类型下,而是引入forwarding-specific notation” 和 “从一开始就有一些人(例如,我和同事)认为“moving”和“forwarding”的合并是一个糟糕的想法,我们勾勒出了“other方式” 但也 “我很遗憾没有投入精力将这些草图作为竞争提案推进。”
谢谢,@JohannesSchaub-litb——正如我所说,从来没有认真提议过。如果桌面上有一个建议以一种方式做,而没有人提供替代建议,那么通常桌面上的那个会获胜。
@Johannes template void ff(for T) … // T deduced to & or &&
对 for
关键字的重新定位多么美妙:)【参考方案2】:
为什么通用引用使用与右值引用相同的语法?这不是不必要的混乱来源吗?委员会有没有考虑过替代语法...
是的,这很令人困惑,IMO(我在这里不同意@JonathanWakely)。我记得在一次关于整体功能早期设计的非正式讨论(我认为是午餐)中,我们确实讨论了不同的符号(Howard Hinnant 和 Dave Abrahams 在那里提出了他们的想法,EDG 人员就如何适应它提供了反馈在核心语言中;这早于 N1377)。我想我记得&?
和&|&&
被考虑过,但所有这些都是口头的;我不知道会议记录已被记录(但我相信这也是 John 建议使用 &&
进行右值引用的时候)。然而,这些是设计的早期阶段,当时有很多基本的语义问题需要考虑。 (例如,在同一次午餐讨论中,我们还提出了不具有两种引用,而是具有两种引用参数的可能性。)
在“类模板参数推导”(P0099R3) 的 C++17 功能中发现了由此引起的混淆的最新方面。通过转换构造函数和构造函数模板的签名形成函数模板签名。对于类似的东西:
template<typename T> struct S
S(T&&);
;
函数模板签名
template<typename T> auto S(T&&)->S<T>;
被形成用于扣除类似的声明
int i = 42;
S s = i; // Deduce S<int> or S<int&>?
在这里推断T = int&
会违反直觉。所以在这种情况下,我们不得不添加一个“特殊扣除规则来禁用特殊扣除规则”:-(
【讨论】:
琐事:这是午餐的地方:google.com/maps/@12.1183502,-68.9663642,196m/data=!3m1!1e3 :-) 谢谢,霍华德。我记得那是一个“热带”的地方,并认为这是一个科纳会议。但是库拉索岛是完全合理的。 感谢您提供有关 C++ 开发如何发生的见解 ;-)【参考方案3】:作为一个只有几年 C++ 经验的开发人员,我必须同意通用引用令人困惑。在我看来,这些东西完全无法理解,更不用说在不阅读 Scott Meyers 和/或观看 YouTube 上的相关演讲的情况下积极使用。
不仅仅是 && 可以是右值引用或“通用引用”,它还是模板用于区分引用类型的方式。想象一下,您是一名从事软件开发的普通工程师。你读到了类似的东西:
template <typename T>
void foo (T&& whatever) ...
然后 foo 的函数体告诉你输入参数必须是在内部库中定义的非常特定的类类型。太好了,您认为,该模板在当前软件版本中已过时,可能是以前开发的遗留物,所以让我们摆脱它。
祝编译器错误好运……
没有明确学习过这一点的人不会假设您在这里将 foo 实现为函数模板,只是为了使用模板推导规则和引用折叠规则,这些规则恰好重合,以便您最终得到所谓的“完美转发” ”。它看起来真的比其他任何东西都更像是一个肮脏的黑客。我认为为通用引用创建语法会使事情变得更简单,并且曾经在旧代码库上工作的开发人员至少会知道这里有 必须用谷歌搜索的东西。只是我的两分钱。
【讨论】:
以上是关于通用引用的语法的主要内容,如果未能解决你的问题,请参考以下文章
从 ForEach 中引用变量时,SwiftUI“无法推断通用参数'数据'”