Combining ranges adaptors and std::source_location got strange results

383 views Asked by At

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?

1

There are 1 answers

6
Barry On BEST ANSWER

gcc is correct, the program is completely valid.

And GCC outputs strange line number 61 no matter which row the std::source_location::current() is in:

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's iterator's operator*(). But not directly by operator*(), that operator is going to call invoke 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 of invoke that gets called is... oh, look at that, it's bits/invoke.h:61:

template<typename _Res, typename _Fn, typename... _Args>
  constexpr _Res
  __invoke_impl(__invoke_other, _Fn&& __f, _Args&&... __args)
  { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); }

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:

auto lines = std::views::iota(0, 5)
           | std::views::transform(
              [](int, const std::source_location& location = std::source_location::current()) 
              { return fmt::format("{}:{}", location.file_name(), location.line()); }
             );

fmt::print("{}\n", lines);

Which prints a range containing the string /opt/compiler-explorer/gcc-trunk-20210817/include/c++/12.0.0/bits/invoke.h:61, five times.