Consider the following useless code:
#include <ranges>
#include <source_location>
#include <iostream>
int main() {
auto lines = std::views::iota(0, 5)
| std::views::transform(
[](int, const std::source_location& location = std::source_location::current())
{ return location.line(); }
);
for (const auto& line : lines)
std::cout << line << "\n";
}
MSVC rejects with the strange error message:
(7): error C2676: binary '|': 'std::ranges::iota_view<_Ty1,_Ty2>' does not define this operator or a conversion to a type acceptable to the predefined operator
with
[
_Ty1=int,
_Ty2=int
]
And GCC outputs strange line number 61 no matter which row the std::source_location::current() is in:
61
61
61
61
61
Is the above code well-formed? If so, does it mean that both MSVC and GCC have bugs?
gcc is correct, the program is completely valid.
That's because the default function argument,
current(), is evaluated at the point of the function call, which has nothing to do with where the function is declared.And this function is called by
transform_view'siterator'soperator*(). But not directly byoperator*(), that operator is going to callinvokewhich itself is going to have to do a bunch of work to make sure it's invoked correctly. And the actual final overload in libstdc++'s implementation ofinvokethat gets called is... oh, look at that, it'sbits/invoke.h:61:This would've been easier to discover if instead of just printing the line number that
source_locationgives you, you also printed the file name:Which prints a range containing the string
/opt/compiler-explorer/gcc-trunk-20210817/include/c++/12.0.0/bits/invoke.h:61, five times.