英文:
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<<
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?
答案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
仅考虑范围0
到5
,最简单的解决方案将是不使用类型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<<
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<<
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论