ericniebler / range-v3

Range library for C++14/17/20, basis for C++20's std::ranges
Other
4.05k stars 437 forks source link

Recursive calls during adl_get #1779

Closed seanbaxter closed 1 year ago

seanbaxter commented 1 year ago

I'm running up against infinite recursion when building some ranges code with circle.

Consider this get function. https://github.com/ericniebler/range-v3/blob/master/include/range/v3/utility/get.hpp#L37

        template<std::size_t I, typename TupleLike>
        constexpr auto CPP_auto_fun(get)(TupleLike &&t)
        (
            return detail::adl_get<I>(static_cast<TupleLike &&>(t))
        )

It's an ADL candidate of the trailing-return-type of adl_get, when passed a type in the ranges namespace: https://github.com/ericniebler/range-v3/blob/master/include/range/v3/detail/adl_get.hpp#L34

            template<std::size_t I, typename TupleLike>
            constexpr auto adl_get(TupleLike && t) noexcept
                -> decltype(get<I>(static_cast<TupleLike &&>(t)))
            {
                return get<I>(static_cast<TupleLike &&>(t));
            }

But that get function at the top is a macro, and that expands the body of the function into a trailing-return-type. After macro expansion it looks like this:

        template<std::size_t I, typename TupleLike>
        constexpr auto get (TupleLike &&t) noexcept(noexcept(decltype( detail::adl_get<I>(static_cast<TupleLike &&>(t)))( detail::adl_get<I>(static_cast<TupleLike &&>(t))))) -> decltype( detail::adl_get<I>(static_cast<TupleLike &&>(t))) { return ( detail::adl_get<I>(static_cast<TupleLike &&>(t))); }

So you have get's trailing return type which calls adl_get, and adl_get's trailing return type does an ADL call back into get. Infinite recursion.

Why is this legal, and what is supposed to prevent the recursion?

Here's the sequence of instantiations:

iter_zip_with_view<...>::cursor<false> instantiation
iter_zip_with_view<...>::cursor::read return type instantiation
tuple_apply_fn::operator() return type instantiation
tuple_apply_fn::impl return type instantiation
detail::adl_get return type instantiation
ranges::get return type instantiation
BACK TO detail::adl_get return type instantiation.
INFINITE RECURSION
seanbaxter commented 1 year ago

Ahh. My compiler was finding ranges::get::get via a using namespace directive in ranges ns. I guess that's not supposed to be used by argument dependent lookup. What obscurantist stuff!

namespace ns {
  struct obj_t { };

  namespace _get_ {
    void get(obj_t);
  }

  using namespace _get_;
}

int main() {
  ns::obj_t obj;
  get(obj);
}
ericniebler commented 1 year ago

The get overloads were a major cause of poor compile times at one point. This tortured implementation avoided the problems. It could probably be simplified now.