禁止对给定的 class、C++14 与 C++17 更新使用自动

Disallow the use of auto for a given class, C++14 vs. C++17 update

允许我在 C++17 中将 auto 用于不可复制(和不可移动)类型而不能用于 C++14 的功能是什么?

考虑以下代码:

struct A{
    A(A const&)=delete;
    A(A&&)=delete;
};

int main(){
    auto   a1 = A{}; // ok in C++17, not ok in C++14
    auto&& a2 = A{}; // ok in C++17, ok in C++14
}

事实证明,这是 C++14 中的无效代码,但在 C++17 中有效。 该行为在 clang 和 gcc 中是一致的:https://godbolt.org/z/af8mEc

我问的原因是因为直到最近我才使我的 classes(代表引用)不可复制,除此之外禁止使用 auto,但不幸的是结果现在该技术在 C++17 中不起作用。

(换句话说,我认为 C++14 中的行为在概念上是正确的。)

为什么 auto a1 = A{}; 在 C++17 中对不可复制的 class 有效?是某种新的 RVO 案例吗?

我认为 auto 由于几个有争议的原因在语义上被破坏但至少在 C++14 中我可以阻止使用 auto(但允许使用 auto&&) .

是否有另一种方法可以防止在 C++17 中对特定的 class 使用 auto a = A{};,或者不再使用?

注意: 我前段时间问过这个问题Is there a way to disable auto declaration for non regular types? 发现当时的解决方案是禁用C++14中的复制和移动构造函数,这在概念和语法上都有意义,但是在 C++17 中不再是这种情况了。

在 C++17 中,如果您为用户提供某种类型的纯右值,用户可以始终使用它来初始化该类型的变量。

本质上,。在 C++14 中,它是一个对象;具体来说,一个临时对象。因此,像A a = prvalue_of_type_A;这样的语句意味着将临时对象移动到a中。此着法可以省略,但它在逻辑上是一个着法,因此 A 必须支持着法构造。

在 C++17 中,纯右值只不过是对象的 初始化程序 。它初始化哪个对象取决于它如何被使用。使用纯右值初始化纯右值类型的变量意味着您初始化该对象。没有复制或移动。你可以从纯右值是一个无名对象的角度来看,A a = prvalue_of_A;只是给那个对象起一个名字,而不是创建一个新对象。

不,你无法绕过它。只要您处理的是某种类型的纯右值,auto a = prvalue; 总是会推断出纯右值的类型并直接初始化对象 a,而没有 copy/move.


In other words, I think the behavior in C++14 was conceptually right.

好吧,让我们调查一下。

这项调查的开始和结束都完全意识到您没有阻止 auto a = A{}; 工作。您阻止了 copy/move 构造,它具有阻止该语法的 副作用 。这就是为什么保证省略使您的 "fix" 毫无意义。

通过拒绝一个类型被copy/move构造的能力,你确实阻止了这种语法。但是在此过程中产生了很多附带损害。

您链接到的 post 给出了要禁用此语法的理由,因为类型是 non-Regular,这使用了 auto "tricky"。 .. 因为某些原因。忽略那些未说明的原因是否有效,您的解决方案不只是删除 "tricky" 语法。它阻止您对对象执行基本操作。

考虑将其应用于 string_view 的后果。您已获得此视图类型,并且希望通过多步骤过程对其进行修改。但是您想保持原始视图不变。所以很自然地,你复制...哦等等,string_view 是非正规的,所以你将其复制为非法只是为了防止 string_view sv = prvalue;。哎呀。所以现在我必须回到 string_view 的原始来源以获取另一个。假设我可以访问源代码,我不只是将它作为 const& 参数传递。

从本质上讲,您是说打苍蝇的最好方法是使用大锤。不管苍蝇多么烦人,它背后的墙可能更重要。

所以,保证省略"conceptually right"?好吧,保证省略的主要理由之一是允许 return 纯右值的函数即使对于固定类型也能工作。这其实很重要。在 C++17 之前,如果你想给你的 class 工厂函数,但 class 本身在逻辑上应该是固定的,那你就不走运了。您可以选择其中之一,但不能同时选择两者。

让我们以您的 subrange 为例。在 C++14 中,如果我想编写一个函数来简单地为一些构造函数的参数提供默认值,或者使用一个特定的容器,或者其他什么,那是不可能的。我什至无法编写一个具有 returnedsubrange 功能的容器,因为我必须构建一个,它(除非我使用raw braced-init-list,这并不总是可能的)会引发 copy/move.

保证省略修复了所有这些问题。您可以使用只能通过工厂创建的私有构造函数来制作固定的 classes。您可以通过 public 接口创建构建固定对象的函数。等等。

C++17 行为使您的固定非常规类型更有用和更强大。那不是"conceptually right"吗?