这两种缩小转换有什么区别?

huangapple go评论69阅读模式
英文:

What's the difference between these two narrowing conversions?

问题

这是一个包含两行执行窄化转换的代码片段。为什么其中一行能够编译通过,而另一行不能呢?就窄化转换而言,它们的操作是相同的。

这只是一个刻意制造的例子。我知道我本可以使用 nums.size() 来获取大小。但这不是问题的重点。

std::vector<int> func(std::vector<int>& nums) {
    int works_fine = nums.end() - nums.begin(); // 这个可以编译通过
    return std::vector<int>({ nums.end() - nums.begin() }); // 这个无法编译通过
}

带有 return 语句的那一行会产生以下警告:

警告:从 __gnu_cxx::operator-<int*, std::vector<int> >((& nums)->std::vector<int>::end(), (& nums)->std::vector<int>::begin()) 进行了窄化转换,从 __gnu_cxx::__normal_iterator<int*, std::vector<int> >::difference_type(也就是 long long int)到 int [-Wnarrowing]

英文:

Here is a code snippet with two lines performing narrowing conversions. Why does one of them compile fine, but the other one doesn't? They are doing the same thing as far as narrowing conversions go.

This is a contrived example. I know I could have just used nums.size() to get the size. This is not the point of the question.

std::vector&lt;int&gt; func(std::vector&lt;int&gt;&amp; nums) {
    int works_fine = nums.end() - nums.begin(); // this compiles
    return std::vector&lt;int&gt;({ nums.end() - nums.begin() }); // this does not compile
}

The line with the return statement gives this warning:

warning: narrowing conversion of '__gnu_cxx::operator-<int*, std::vector<int> >((& nums)->std::vector<int>::end(), (& nums)->std::vector<int>::begin())' from '__gnu_cxx::__normal_iterator<int*, std::vector<int> >::difference_type' {aka 'long long int'} to 'int' [-Wnarrowing]

答案1

得分: 4

获取末迭代器的索引方式没有问题。

然而,在您的 std::vector 初始化中存在问题。有两个相关的构造函数。一个接受一个参数,参数类型为 std::vector&lt;int&gt;::size_type(通常是 std::size_t),另一个参数类型为 std::initializer_list&lt;int&gt;

问题在于,std::initializer_list 在重载解析中是特殊的,总是优先于其他参数类型的转换进行列表初始化,也就是当参数是一个大括号初始化列表(即不是表达式,而是以 { 开头的列表)时。

所以在这里选择的构造函数是接受 std::initializer_list&lt;int&gt; 的那个,它用该列表的元素初始化向量。

然后问题是,要构造 std::initializer_list&lt;int&gt;int 元素,您必须将 nums.end() - nums.begin() 转换为 int 类型。nums.end() - nums.begin() 的类型是 std::vector&lt;int&gt;::difference_type(通常是 std::ptrdiff_t),这可能比 int 宽,因此这是一种缩小转换。

在列表初始化中,是不允许缩小转换的。(GCC 已经非常宽容了,仅提供警告而不是编译失败。其他编译器可能不太宽容,但这两种行为都是符合规范的。)

根据您的评论,这确实是您想要使用的构造函数,因此:

要使其正确编译,您需要显式将 nums.end() - nums.begin() 转换为 int

std::vector&lt;int&gt;({static_cast&lt;int&gt;(nums.end() - nums.begin())});

或者您可以使用一个不使用列表初始化的等效构造函数:

std::vector&lt;int&gt;(1, nums.end() - nums.begin());

无论哪种情况,警告/错误都是有道理的:如果索引大于 int 可以容纳的范围,您希望发生什么?上述两种解决方案都会忽略这一点,只会在向量中存储一个意外的值。要正确处理这个问题,您需要重新考虑向量的 int 元素类型。它可能应该是 std::ptrdiff_t,或者甚至更好的是 std::vector&lt;int&gt;::difference_type。或者您可以添加一个超出范围的检查,并且根据您的用例执行合理的操作,例如抛出异常或执行其他合适的操作。

英文:

There is nothing wrong with how you obtain the index of the end iterator.


However, there is a problem with your std::vector initialization. There are two relevant constructors. One that accepts a single argument for a parameter of type std::vector&lt;int&gt;::size_type (usually std::size_t) and one that has a single parameter of type std::initializer_list&lt;int&gt;.

The issue is thatstd::initializer_list's are special in overload resolution and always preferred over conversion to other parameter types in list-initialization, i.e. when the argument is a braced-init-list (i.e. not an expression but instead a starting with a {).

So the constructor chosen here is the one that takes the std::initializer_list&lt;int&gt; and that initializes the vector with the elements of that list.

Then the problem is that to construct the int element of the std::initializer_list&lt;int&gt; you must convert nums.end() - nums.begin() to type int. The type of nums.end() - nums.begin() is std::vector&lt;int&gt;::difference_type (usually std::ptrdiff_t), which is likely to be wider than int, so that this is a narrowing conversion.

And narrowing conversions are not permitted in list-initialization. (GCC is already unusually permissive by only providing a warning instead of failing to compile it. Other compilers are not that permissive, but both behaviors are conforming.)


According to your comments this really is the constructor that you want to use, so:

To make it compile properly you need to explicitly cast nums.end() - nums.begin() to int:

std::vector&lt;int&gt;({static_cast&lt;int&gt;(nums.end() - nums.begin())});

or you can use an equivalent constructor that doesn't use list-initialization:

std::vector&lt;int&gt;(1, nums.end() - nums.begin());

In either case, the warning/error is there for a good reason: What do you want to happen if the index is larger than int can hold? Both solutions given above ignore this and will simply store an unintended value in the vector. To do this properly you need to rethink the int element type for the vector. It should probably be std::ptrdiff_t or even better std::vector&lt;int&gt;::difference_type. Alternatively you can add an out-of-range check and e.g. throw an exception or do whatever is sensible to your use case.

huangapple
  • 本文由 发表于 2023年5月29日 01:55:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76352857.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定