Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++] compile fails when using std::unitialized_copy() with an output iterator type that has operator!=() overload #69334

Closed
tearfur opened this issue Oct 17, 2023 · 2 comments · Fixed by #69373
Assignees
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. rejects-valid

Comments

@tearfur
Copy link

tearfur commented Oct 17, 2023

Description

When using std::uninitialized_copy() from libc++:

template< class InputIt, class NoThrowForwardIt >
NoThrowForwardIt uninitialized_copy( InputIt first, InputIt last,
                                     NoThrowForwardIt d_first );

If the user overloaded operator!=() like this:

template< class Iter >
bool operator!=( NoThrowForwardIt const& lhs, Iter const& rhs );

Compilation fails due to ambiguous overload resolution.

Minimal reproducible example

main.cpp
#include <array>
#include <iostream>
#include <iterator>
#include <memory>

namespace
{
template<typename T>
class myCrappyIterator
{
public:
    using value_type = typename std::iterator_traits<T*>::value_type;
    using difference_type = typename std::iterator_traits<T*>::difference_type;
    using reference = typename std::iterator_traits<T*>::reference;
    using pointer = typename std::iterator_traits<T*>::pointer;
    using iterator_category = typename std::iterator_traits<T*>::iterator_category;

    myCrappyIterator() = default;

    explicit myCrappyIterator(T* const ptr)
        : ptr_(ptr)
    {
    }

    myCrappyIterator(myCrappyIterator const& that) = default;
    myCrappyIterator(myCrappyIterator&& that) = default;
    myCrappyIterator<T>& operator=(myCrappyIterator const& that) = default;
    myCrappyIterator<T>& operator=(myCrappyIterator&& that) = default;

    constexpr reference operator*() const noexcept
    {
        return *ptr_;
    }

    constexpr pointer operator->() const noexcept
    {
        return std::addressof(*ptr_);
    }

    constexpr myCrappyIterator& operator++() noexcept
    {
        ++ptr_;
        return *this;
    }

    constexpr myCrappyIterator operator++(int) const noexcept
    {
        myCrappyIterator old{ *this };
        ++(*this);
        return old;
    }

    constexpr bool operator==(myCrappyIterator const& that) const noexcept
    {
        return this->ptr_ == that.ptr_;
    }

    constexpr bool operator!=(myCrappyIterator const& that) const noexcept
    {
        return !this->operator==(that);
    }

private:
    pointer ptr_;

    template<typename Iter>
    friend bool operator!=(myCrappyIterator const& lhs, Iter const& rhs) noexcept;
};

template<typename T, typename Iter>
constexpr bool operator!=(myCrappyIterator<T> const& lhs, Iter const& rhs) noexcept
{
    return lhs.ptr_ == rhs;
}
} // namespace

int main()
{
    static auto constexpr src = std::array{ 1, 2, 3, 4 };
    auto dst = decltype(src){};

    std::uninitialized_copy(std::begin(src), std::end(src), myCrappyIterator{ std::data(dst) });

    return 0;
}

clang console output:

In file included from main.cpp:1:
In file included from /usr/include/c++/v1/array:114:
In file included from /usr/include/c++/v1/algorithm:667:
In file included from /usr/include/c++/v1/functional:506:
In file included from /usr/include/c++/v1/__functional/function.h:24:
In file included from /usr/include/c++/v1/memory:819:
In file included from /usr/include/c++/v1/__memory/ranges_uninitialized_algorithms.h:22:
/usr/include/c++/v1/__memory/uninitialized_algorithms.h:45:41: error: use of overloaded operator '!=' is ambiguous (with operand types '(anonymous namespace)::myCrappyIterator<int>' and 'std::__unreachable_sentinel')
    for (; __ifirst != __ilast && __idx != __olast; ++__ifirst, (void)++__idx)
                                  ~~~~~ ^  ~~~~~~~
/usr/include/c++/v1/__memory/uninitialized_algorithms.h:61:26: note: in instantiation of function template specialization 'std::__uninitialized_copy<int, const int *, const int *, (anonymous namespace)::myCrappyIterator<int>, std::__unreachable_sentinel>' requested here
  auto __result = _VSTD::__uninitialized_copy<_ValueType>(_VSTD::move(__ifirst), _VSTD::move(__ilast),
                         ^
main.cpp:82:10: note: in instantiation of function template specialization 'std::uninitialized_copy<const int *, (anonymous namespace)::myCrappyIterator<int>>' requested here
    std::uninitialized_copy(std::begin(src), std::end(src), myCrappyIterator{ std::data(dst) });
         ^
/usr/include/c++/v1/__memory/uninitialized_algorithms.h:30:55: note: candidate function [with _Iter = (anonymous namespace)::myCrappyIterator<int>]
  _LIBCPP_HIDE_FROM_ABI friend _LIBCPP_CONSTEXPR bool operator!=(const _Iter&, __unreachable_sentinel) _NOEXCEPT {
                                                      ^
main.cpp:67:17: note: candidate function [with Iter = std::__unreachable_sentinel]
    friend bool operator!=(myCrappyIterator const& lhs, Iter const& rhs) noexcept;
                ^
main.cpp:71:16: note: candidate function [with T = int, Iter = std::__unreachable_sentinel]
constexpr bool operator!=(myCrappyIterator<T> const& lhs, Iter const& rhs) noexcept
               ^
1 error generated.
@github-actions github-actions bot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Oct 17, 2023
@ldionne
Copy link
Member

ldionne commented Oct 17, 2023

This happens because we use a helper type __unreachable_sentinel in our implementation of std:: uninitialized_copy:

struct __unreachable_sentinel {
  template <class _Iter>
  friend constexpr bool operator!=(const _Iter&, __unreachable_sentinel) noexcept {
    return true;
  }
};

template <class _ValueType, class _InputIterator, class _Sentinel1, class _ForwardIterator, class _Sentinel2>
pair<_InputIterator, _ForwardIterator>
__uninitialized_copy(_InputIterator __ifirst, _Sentinel1 __ilast, _ForwardIterator __ofirst, _Sentinel2 __olast) {
  _ForwardIterator __idx = __ofirst;
  try {
    for (; __ifirst != __ilast && __idx != __olast; ++__ifirst, (void)++__idx)
      ::new (std::__voidify(*__idx)) _ValueType(*__ifirst);
  } catch (...) {
    std::__destroy(__ofirst, __idx);
    throw;
  }

  return pair<_InputIterator, _ForwardIterator>(std::move(__ifirst), std::move(__idx));
}

template <class _InputIterator, class _ForwardIterator>
_ForwardIterator uninitialized_copy(_InputIterator __ifirst, _InputIterator __ilast, _ForwardIterator __ofirst) {
  typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
  auto __result = std::__uninitialized_copy<_ValueType>(
      std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __unreachable_sentinel());
  return std::move(__result.second);
}

The goal of this is to reuse the same implementation of __uninitialized_copy for ranges and for classic algorithms. However, it adds a requirement to the iterator type and that makes us non-conforming.

@phyBrackets
Copy link
Member

Wondering, can we make it specialized using SFINAE (std::enable_if) to ensure that it only matches when one of the operands is __unreachable_sentinel?

@ldionne ldionne self-assigned this Oct 17, 2023
ldionne added a commit to ldionne/llvm-project that referenced this issue Oct 17, 2023
…ison operators

If an iterator passed to std::uninitialized_copy & friends provided an
unconstrained comparison operator, we would trigger an ambiguous overload
resolution because we used to compare against __unreachable_sentinel in
our implementation.

This patch fixes that by only comparing the output iterator when it is
actually required, i.e. in the <ranges> versions of the algorithms.

Fixes llvm#69334
ldionne added a commit that referenced this issue Oct 20, 2023
…ison operators (#69373)

If an iterator passed to std::uninitialized_copy & friends provided an
unconstrained comparison operator, we would trigger an ambiguous
overload resolution because we used to compare against
__unreachable_sentinel in our implementation.

This patch fixes that by only comparing the output iterator when it is
actually required, i.e. in the <ranges> versions of the algorithms.

Fixes #69334
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. rejects-valid
Projects
None yet
3 participants