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 callinvoke
which 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 ofinvoke
that 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_location
gives 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.