为什么“通用参考"具有与右值引用相同的语法?

2022-01-05 00:00:00 reference c++ c++11 rvalue universal

我刚刚对那些(相当)新特性进行了一些研究,我想知道为什么 C++ 委员会决定为它们引入相同的语法?似乎开发人员没有必要浪费一些时间来了解它是如何工作的,一个解决方案让我们考虑进一步的问题.就我而言,它是从问题开始的,可以简化为:

I just made some research about those (quite) new features and I wonder why C++ Committee decided to introduce the same syntax for both of them? It seems that developers unnecessary have to waste some time to understand how it works, and one solution lets to think about further problems. In my case it started from problem which can be simplified to this:

#include <iostream>

template <typename T>
void f(T& a)
{
    std::cout << "f(T& a) for lvalues
";
}

template <typename T>
void f(T&& a)
{
    std::cout << "f(T&& a) for rvalues
";
}

int main()
{
    int a;
    f(a);
    f(int());

    return 0;
}

我首先在 VS2013 上编译它,它按我的预期工作,结果如下:

I compiled it firstly on VS2013 and it worked as I expected, with this results:

f(T& a) for lvalues
f(T&& a) for rvalues

但是有一件可疑的事情:intellisense 下划线 f(a).我做了一些研究,我明白这是因为类型折叠(Scott Meyers 命名的通用引用),所以我想知道 g++ 是怎么想的.当然,它没有编译.很高兴微软实现了他们的编译器以更直观的方式工作,但我不确定它是否符合标准,是否应该在 IDE 中存在这种差异(编译器与智能感知,但实际上可能有有点意思).好,回到问题.我是这样解决的:

But there was one suspicious thing: intellisense underlined f(a). I made some research and I understood that it is because type collapsing (universal references as Scott Meyers named it), so I wondered what g++ thinks about it. Of course it didn't compiled. It is very nice that Microsoft implemented their compiler to work in more intuitive way, but I'm not sure if it is according to the standard and if there should be this kind of difference in IDE (compiler vs intellisense, but in fact there may be some sense in it). Ok, return to the problem. I solved it in this way:

template <typename T>
void f(T& a)
{
    std::cout << "f(T& a) for lvalues
";
}

template <typename T>
void f(const T&& a)
{
    std::cout << "f(T&& a) for rvalues
";
}

现在没有任何类型崩溃,只是 (r/l) 值的正常重载.它在 g++ 上编译,intellisense 停止抱怨,我几乎满意.几乎,因为我想过如果我想改变通过右值引用传递的对象状态中的某些东西怎么办?我可以在必要时描述一些情况,但是这个描述太长了,无法在这里展示.我是这样解决的:

Now there wasn't any type collapsing, just normal overloading for (r/l)values. It compiled on g++, intellisense stopped complaining and I was almost satisfied. Almost, because I thought about what if I will want to change something in object's state which is passed by rvalue reference? I could describe some situation when it could be necessary, but this description is too long to present it here. I solved it in this way:

template <typename T>
void f(T&& a, std::true_type)
{
    std::cout << "f(T&& a) for rvalues
";
}

template <typename T>
void f(T&& a, std::false_type)
{
    std::cout << "f(T&& a) for lvalues
";
}

template <typename T>
void f(T&& a)
{
    f(std::forward<T>(a), std::is_rvalue_reference<T&&>());
}

现在它可以在所有经过测试的编译器上编译,它允许我更改右值引用实现中的对象状态,但它看起来不太好,这是因为通用引用和右值引用的语法相同.所以我的问题是:为什么 C++ 委员会没有为通用引用引入另一种语法?我认为这个特性应该被标记,例如,通过 T?、auto? 或类似的东西,但不是 T&&和自动&&它只是与右值引用发生冲突.使用这种方法,我的第一个实现将是完全正确的,不仅适用于 MS 编译器.谁能解释一下委员会的决定?

Now it compiles on all tested compilers and it lets me to change object state in rvalue reference implementation, but it doesn't looks very nice, and this is because of the same syntax for universal references and rvalue references. So my question is: Why C++ Committee didn't introduce some another syntax for universal references? I think that this feature should be signalized, for example, by T?, auto?, or something similar, but not as T&& and auto&& which just collide with rvalue references. Using this approach my first implementation would be perfectly correct, not only for MS compiler. Can anyone explain Committee decision?

推荐答案

我认为情况正好相反.最初的想法是在语言中引入右值引用,这意味着提供双与号引用的代码不关心被引用对象会发生什么".这允许移动语义.这个不错.

I think it happened the other way around. The initial idea was to introduce rvalue-references into the language, meaning that "the code providing the double-ampersand reference does not care about what will happen to the referred-to object". This permits move semantics. This is nice.

现在.该标准禁止构建对引用的引用,但这始终是可能的.考虑:

Now. The standard forbids constructing a reference to a reference, but this was always possible. Consider:

template<typename T>
void my_func(T, T&) { /* ... */ }

// ...

my_func<int&>(a, b);

在这种情况下,第二个参数的类型应该是 int &&,但这在标准中是明确禁止的.所以引用必须被折叠,即使在 C++98 中也是如此.C++98中只有一种引用,所以折叠规则很简单:

In this case the type of the second parameter should be int & &, but this is explicitly forbidden in the standard. So the references have to be collapsed, even in C++98. In C++98, there was only one kind of reference, so the collapsing rule was simple:

& & -> &

现在,我们有两种引用,其中 && 的意思是我不关心对象会发生什么",以及 &意思是我可能关心对象可能会发生什么,所以你最好注意你在做什么".考虑到这一点,折叠规则自然而然地流动:C++ 应该折叠对 && 的引用,只有在没有人关心对象发生了什么的情况下:

Now, we have two kinds of references, where && means "I don't care about what may happen to the object", and & meaning "I may care about what may happen to the object, so you better watch what you're doing". With this in mind, the collapsing rules flow naturally: C++ should collapse referecnces to && only if no one cares about what happens to the object:

& & -> &
& && -> &
&& & -> &
&& && -> &&

有了这些规则,我认为是 Scott Meyers 注意到了这部分规则:

With these rules in place, I think it's Scott Meyers who noticed that this subset of rules:

& && -> &
&& && -> &&

表明 && 在引用折叠方面是右中性的,并且当发生类型推导时,T&& 构造可用于匹配任何类型的参考,并为这些参考创造了术语通用参考".这不是委员会发明的东西.这只是其他规则的副作用,不是委员会的设计.

Shows that && is right-neutral with regards to reference collapsing, and, when type deduction occurs, the T&& construct can be used to match any type of reference, and coined the term "Universal reference" for these references. It is not something that has been invented by the Committee. It is only a side-effect of other rules, not a Committee design.

因此引入这个术语是为了区分真正的右值引用,当没有类型推导发生时,保证是 &&,和那些类型推导的 UNIVERSAL 引用,它不保证在模板特化时保持 &&.

And the term has therefore been introduced to distinguish between REAL rvalue-references, when no type deduction occurs, which are guaranteed to be &&, and those type-deduced UNIVERSAL references, which are not guaranteed to remain && at template specialization time.

相关文章