https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94679

Jonathan Wakely <redi at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
          Component|libstdc++                   |c++
     Ever confirmed|0                           |1
           Keywords|                            |link-failure
   Last reconfirmed|                            |2020-04-21
             Status|UNCONFIRMED                 |NEW

--- Comment #1 from Jonathan Wakely <redi at gcc dot gnu.org> ---
That function is intentionally not defined, and only used in unevaluated
contexts.

It seems to come from the sortable constraint on next_permutation, because
commenting out the requires-clause fixes the link failure:

    template<bidirectional_range _Range, typename _Comp = ranges::less,
             typename _Proj = identity>
      requires sortable<iterator_t<_Range>, _Comp, _Proj>

The sortable concept uses projected as a template argument to another concept:

  template<typename _Iter, typename _Rel = ranges::less,
           typename _Proj = identity>
    concept sortable = permutable<_Iter>
      && indirect_strict_weak_order<_Rel, projected<_Iter, _Proj>>;

That ends up being used with this constrained alias template:

  template<__detail::__dereferenceable _Tp>
    requires requires(_Tp& __t)
    { { ranges::iter_move(__t) } -> __detail::__can_reference; }
    using iter_rvalue_reference_t
      = decltype(ranges::iter_move(std::declval<_Tp&>()));

Which uses:

  namespace ranges
  {
    namespace __cust_imove
    {
      void iter_move();

      template<typename _Tp>
        concept __adl_imove
          = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>)
          && requires(_Tp&& __t) { iter_move(static_cast<_Tp&&>(__t)); };

      struct _IMove
      {
        // [...]

        template<typename _Tp>
          requires __adl_imove<_Tp> || requires(_Tp& __e) { *__e; }
          constexpr decltype(auto)
          operator()(_Tp&& __e) const
          // noexcept([...])
          {
            if constexpr (__adl_imove<_Tp>)
              return iter_move(static_cast<_Tp&&>(__e));
            else if constexpr (is_reference_v<iter_reference_t<_Tp>>)
              return std::move(*__e);
            else
              return *__e;
          }
      };
    } // namespace __cust_imove

    inline namespace __cust
    {
      inline constexpr __cust_imove::_IMove iter_move{};
    } // inline namespace __cust
  } // namespace ranges


So it appears that determining the return type of that function in an
unevaluated context causes its instantiation to be kept, leaving a reference to
the undefined projected::operator*() function.

We might be able to avoid using return type deduction for _IMove::operator()
but I wonder if this is really a compiler bug. I imagine this same problem
could occur for other uses of return type deduction.

Reply via email to