编译器为什么无法在`size_t`变量上自动匹配类型,以用于范围基于循环?

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

Why is the compiler unable to match the types automatically on `size_t` variables in a ranged base for loop?

问题

I stumbled upon a problem with ambiguous overload for operator<< when using std::views::enumerate with size_t range.
More specifically using this code:

#include <iostream>
#include <ranges>
namespace rv = std::ranges::views;

int main()
{
    for (const auto& [idx, value] : rv::iota(0zu, 5zu) | rv::enumerate)
        std::cout << idx << '\n';
}

and compiling with gcc 13.1.1 on linux using: g++ --std=c++23 main.cpp.
I get the error:

main.cpp: In function 'int main()':
main.cpp:11:19: error: ambiguous overload for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'std::tuple_element<0, const std::tuple<__int128, long unsigned int>>::type' {aka 'const __int128'})
   11 |         std::cout << idx << '\n';
      |         ~~~~~~~~~ ^~ ~
      |              |       |
      |              |       std::tuple_element<0, const std::tuple<__int128, long unsigned int>>::type {aka const __int128}
      |              std::ostream {aka std::basic_ostream<char>}

and then a bunch of candidates for the << operator.

This can be remediated by casting idx (e.g., using std::cout << (size_t)idx) but it seems unnecessary tedious.
The problem seems to only occur when using long variables as start and end for the range.
For instance if we use rv::iota(begin, end) with either int begin{0}, end{5} (whether signed or unsigned) the problem disappears.

Is this a simple bug at the compiler level or is there something deeper preventing it from matching the correct type?

英文:

I stumbled upon a problem with ambiguous overload for operator&lt;&lt; when using std::views::enumerate with size_t range.
More specifically using this code:

#include &lt;iostream&gt;
#include &lt;ranges&gt;
namespace rv = std::ranges::views;

int main()
{
    for (const auto&amp; [idx, value] : rv::iota(0zu, 5zu) | rv::enumerate)
        std::cout &lt;&lt; idx &lt;&lt; &#39;\n&#39;;
}

and compiling with gcc 13.1.1 on linux using: g++ --std=c++23 main.cpp.
I get the error:

main.cpp: In function ‘int main()’:
main.cpp:11:19: error: ambiguous overload for ‘operator&lt;&lt;’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream&lt;char&gt;’} and ‘std::tuple_element&lt;0, const std::tuple&lt;__int128, long unsigned int&gt; &gt;::type’ {aka ‘const __int128’})
   11 |         std::cout &lt;&lt; idx &lt;&lt; &#39;\n&#39;;
      |         ~~~~~~~~~ ^~ ~
      |              |       |
      |              |       std::tuple_element&lt;0, const std::tuple&lt;__int128, long unsigned int&gt; &gt;::type {aka const __int128}
      |              std::ostream {aka std::basic_ostream&lt;char&gt;}

and then a bunch of candidates for the &lt;&lt; operator.

This can be remediated by casting idx (e.g., using std::cout &lt;&lt; (size_t)idx) but it seems unnecessary tedious.
The problem seems to only occur when using long variables as start and end for the range.
For instance if we use rv::iota(begin, end) with either int begin{0}, end{5} (whether signed or unsigned) the problem disappears.

Is this a simple bug at the compiler level or is there something deeper preventing it from matching the correct type?

答案1

得分: 7

idx的类型是iota视图的difference_type

iota的参数中,您使用了类型std::size_t。可能在您的系统上,std::size_t具有64位宽度,这也可能是您的系统上任何整数类型的最大宽度。

现在的问题是,iota视图的difference_type不能也是64位宽度的类型,因为这样的类型不能够容纳范围内任意两个项目之间的差异。

因此,iota被规定具有比元素类型宽度更大的difference_type。如果存在具有该属性的有符号整数类型,那么difference_type将是有符号整数类型。如果不存在这样的整数,则difference_type将是具有足够宽度的有符号整数样式类型,即在某些重要方面行为类似于有符号整数类型但不是有符号整数类型。

它可以是具有适当重载运算符的类类型,或者正如您所看到的那样,某种实现特定类型,不被视为整数类型,但具有类似的行为。(实际上,GCC是否将__int128视为扩展整数类型取决于您是否使用-std=gnu++23-std=c++23。)

这些类型需要满足的属性列表,请参阅iterator.concept.winc

但是,std::ostream::operator<<仅对标准整数类型进行了重载。如果idx的类型只是有符号整数样式的类型,则没有完全匹配的重载。

您会收到模糊错误,因为实现选择的具体类型,即__int128_t,可以隐式转换为所有具有相同转换等级的标准有符号整数类型。 (如果__int128_t被视为(扩展)有符号整数类型,情况也是如此。)但这是一个实现细节。甚至不能保证有符号整数样式的类型隐式转换为任何整数类型(因为它们都具有较小的宽度)。

使用例如static_cast或C风格转换的显式转换可确保适用于任何整数类型,但可能会导致结果变窄。

因此,这里没有编译器错误。这是允许的行为,您不能依赖能够直接使用operator<<打印idx

如果您打算使iota仅考虑范围05,最简单的解决方案将是不使用类型std::size_t,而是使用足够大小的较小类型,例如,如果您知道int是32位并且您在64位系统上,则使用rv::iota(0, 5)

如果您不打算冒狭窄的风险,也不想依赖知道系统上存在具有较大宽度的标准整数类型,那么您将不得不将idx的值转换为十进制字符串表示自己。通常的算术运算符可以保证在预期的方式上在有符号整数样式的类型上工作,因此这是可能的。但我认为目前没有任何标准库函数将任何有符号整数样式的类型转换为十进制字符串表示。

英文:

The type of idx is the difference_type of the iota view.

You are using the type std::size_t for the arguments to iota. Probably std::size_t has 64 bit width on your system and that is probably also the maximum width of any integer type on your system.

The problem now is that difference_type for the iota view cannot also be a 64 bit width type, since such a type wouldn't be able hold the difference between any two items in the range.

Therefore iota is specified to have a difference_type that has larger width than the width of the element type. If a signed integer type with that property exists, then difference_type will be a signed integer type. If such an integer doesn't exits, then difference_type will be a signed-integer-like type with sufficient width, i.e. a type that behaves in certain important ways like a signed integer type, but isn't one.

It could be e.g. a class type with properly overloaded operators or, as you are seeing, some implementation-specific type that is not considered an integer type, but has similar behavior. (Actually, whether GCC considers __int128 an extended integer type or not depends on whether you use -std=gnu++23 or -std=c++23.)

For a list of properties these types need to satisfy see [iterator.concept.winc].

However, std::ostream::operator&lt;&lt; is overloaded only for the standard integer types. idx's type therefore doesn't have an exactly matching overload if it is only a signed-integer-like type.

You get the ambiguity error because the concrete type the implementation chose for idx, i.e. __in128_t, is implicitly convertible to all of the standard signed integer types with same conversion rank. (That would also be the case if __int128_t was considered an (extended) signed integer type.) However, this is an implementation detail. It isn't even guaranteed that the signed-intger-like type is implicitly convertible to any of the integer types (because they all have smaller width).

The explicit conversion with e.g. static_cast or a C-style cast is guaranteed to work to any integer type, but risks narrowing the result.

So there is no compiler bug here. This is the allowed behavior and you can't rely on being able to print idx with operator&lt;&lt; directly.

If you intent iota to only consider a range of 0 to 5, the easiest solution would be to not use type std::size_t, but a smaller type of sufficient size instead, e.g. rv::iota(0, 5) if you know that int is 32 bit and you are on a 64 bit system.

If you do not intent to risk narrowing and you don't want to rely on knowing that there is a standard integer type with larger width on your system, then you'll have to convert the value of idx to a decimal string representation yourself. The usual arithmetic operators are guaranteed to work on the signed-integer-like type as expected, so that this is possible. But I don't think there is currently any standard library function that converts any signed-integer-like type to a decimal string representation.

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

发表评论

匿名网友

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

确定