禁止对给定的类C ++ 14与C ++ 17更新使用auto [英] Disallow the use of auto for a given class, C++14 vs. C++17 update

查看:118
本文介绍了禁止对给定的类C ++ 14与C ++ 17更新使用auto的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

什么功能使我可以将auto用于C ++ 17中的不可复制(和不可移动)类型,而不能用于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

我问的原因是因为直到最近我才使我的类(表示引用)不可复制,其中包括禁止使用auto,但是不幸的是,事实证明现在该技术无法在C ++ 17.

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

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

我认为auto在语义上由于多种原因而被破坏,但至少在C ++ 14中,我可以阻止使用auto(但允许使用auto&&).

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

注意:我前段时间解决方案

在C ++ 17中,如果为用户提供某种类型的prvalue,则用户可以始终使用它来初始化该类型的变量.

本质上, prvalue的定义已更改.在C ++ 14中,它是一个对象.具体来说,是一个临时对象.因此,类似A a = prvalue_of_type_A;的语句意味着将临时对象移到a中.可以忽略此举动,但是从逻辑上讲这是举动,因此A必须支持举动构造.

在C ++ 17中,prvalue仅仅是对象的初始化器.初始化哪个对象取决于如何使用它.使用prvalue初始化prvalue类型的变量意味着您初始化了该对象.没有副本或移动.您可以从prvalue是一个无名对象的角度来看它,而A a = prvalue_of_A;只是给该对象起一个名字,而不是创建一个新对象.

不,您无法解决它.只要您要处理某种类型的真正prvalue,auto a = prvalue;就会始终推断出prvalue的类型并直接初始化对象a,而无需复制/移动.


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

好吧,让我们调查一下.

此调查的开始和结束是完全意识到您没有阻止auto a = A{};正常工作.您阻止了复制/移动构造,该构造具有防止该语法的副作用.这就是为什么保证省略会使您的修正"变得毫无意义的原因.

通过拒绝某种类型的复制/移动构造功能,您确实确实避免了这种语法.但是在此过程中发生了很多附带损害.

您链接到的帖子给出了由于类型为

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

本质上,您是说拍打苍蝇的最佳方法是使用大锤.不管苍蝇有多烦人,它后面的墙可能都更重要.

那么,保证省略在概念上是正确的吗?好吧,保证省略的主要理由之一是允许返回prvalue的函数甚至对于不动的类型也可以工作.这实际上很重要.在C ++ 17之前,如果您想为类提供工厂功能,但是从逻辑上讲类本身应该是固定不变的,那么您就不走运了.您可以选择一个,但不能两个都选.

让我们以您的subrange为例.在C ++ 14中,如果我想编写一个仅为某些构造函数的参数提供默认值的函数,或者使用特定的容器等,那是不可能的.我什至不能编写一个容器,该容器具有返回一个subrange的功能,因为我必须构造一个容器,除非(除非我使用了原始的braced-init-list,否则它是"t总是有可能)会引起复制/移动.

保证的清除解决了所有这些问题.您可以使用只能通过工厂创建的私有构造函数来制作固定类.您可以创建通过公共接口构建固定对象的函数.依此类推.

C ++ 17的行为使不固定的非常规类型更加有用和强大.那不是概念上正确"吗?

What is the feature that allows me use auto for non-copyable (and non-movable) types in C++17 and didn't for C++14?

Consider the following code:

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
}

It turns out that this was invalid code in C++14 but it is valid in C++17. The behavior is consistent in clang and gcc: https://godbolt.org/z/af8mEc

The reason I ask is because until recently I was making my classes (that represent references) non-copyable, among other things to disallow use of auto, but unfortunately it turns out that now the technique doesn't work in C++17.

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

Why is auto a1 = A{}; valid for a non-copyable class in C++17? Is some kind of new RVO case?

I think auto is semantically broken for several arguable reasons but at least in C++14 I could prevent the use of auto (but allowed the use of auto&&).

Is there another way at all of preventing the use of auto a = A{}; for a particular class in C++17, or not anymore?

Note: I asked this question some time ago Is there a way to disable auto declaration for non regular types? and found that the solution back then was to disable the copy and move constructors in C++14, this made both conceptual and syntactic sense however this is not the case anymore in C++17.

解决方案

In C++17, if you give a user a prvalue of some type, the user can always use it to initialize a variable of that type.

Essentially, the definition of a prvalue has changed. In C++14, it was an object; specifically, a temporary object. Therefore, a statement like A a = prvalue_of_type_A; means to move the temporary object into a. The move could be elided, but it is logically a move and therefore A must support move construction.

In C++17, a prvalue is nothing more than an initializer for an object. Which object it initializes depends on how it gets used. Using a prvalue to initialize a variable of the prvalue's type means that you initialize that object. There is no copy or move. You can look at it from the perspective that the prvalue is a nameless object, and A a = prvalue_of_A; is merely giving a name to that object, not creating a new object.

And no, you can't get around it. So long as you are dealing with a genuine prvalue of some type, auto a = prvalue; will always deduce the type of the prvalue and directly initialize the object a, with no copy/move.


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

Well, let's investigate that.

This investigation begins and ends with the full realization that you did not prevent auto a = A{}; from working. You prevented copy/move construction, which has the side-effect of preventing that syntax. This is why guaranteed elision made your "fix" meaningless.

By denying a type the ability to be copy/move constructed, you did indeed prevent this syntax. But there was a whole lot of collateral damage incurred along the way.

The post you linked to gave the rationale for wanting to disable this syntax as the type is non-Regular, and this makes the use of auto "tricky"... for some reason. Ignoring whether those unstated reasons are valid, your solution didn't just remove the "tricky" syntax. It prevented you from doing basic things to the object.

Consider the ramifications of applying this to string_view. You've got this view type, and you want to modify it through a multi-step process. But you want to keep the original view around unmodified. So naturally, you copy the... oh wait, string_view is non-Regular, so you made copying it illegal just to prevent string_view sv = prvalue;. Oops. So now I have to go back to my original source of the string_view to get another one. Assuming I have access to the source, that I wasn't just passed it as a const& parameter.

Essentially, you're saying that the best way to swat a fly is to use a sledgehammer. No matter how annoying the fly is, the wall behind it was probably more important.

So, is guaranteed elision "conceptually right"? Well, one of the main justifications of guaranteed elision was to allow functions that return prvalues to work even for types that are immobile. This is actually quite important. Before C++17, if you wanted to give your class factory functions, but the class itself should logically be immobile, you were out of luck. You could pick one or the other, but not both.

Let's take your subrange example. In C++14, if I wanted to write a function that simply provided defaults for some of the constructor's parameters, or used a specific container, or whatever, that wasn't possible. I couldn't even write a container that had a function which returned a subrange, since I would have to construct one, which (unless I used a raw braced-init-list, which isn't always possible) would provoke a copy/move.

Guaranteed elision fixes all of these problems. You can make immobile classes with private constructors which can only be created through factories. You can make functions that build immobile objects through public interfaces. And so forth.

The C++17 behavior makes your immobile non-Regular types more useful and capable. Is that not "conceptually right"?

这篇关于禁止对给定的类C ++ 14与C ++ 17更新使用auto的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆