In [alg.unique], the signature of ranges::unique_copy
is defined as:
template<input_iterator I, sentinel_for<I> S, weakly_incrementable O, class Proj = identity,
indirect_equivalence_relation<projected<I, Proj>> C = ranges::equal_to>
requires indirectly_copyable<I, O> &&
(forward_iterator<I> ||
(input_iterator<O> && same_as<iter_value_t<I>, iter_value_t<O>>) ||
indirectly_copyable_storable<I, O>)
constexpr ranges::unique_copy_result<I, O>
ranges::unique_copy(I first, S last, O result, C comp = {}, Proj proj = {});
But I found that when I
is only an input_iterator
and O
is only an output_iterator
, the following code fails to compile:
std::istringstream str("42 42 42");
std::ranges::unique_copy(
std::istream_iterator<int>(str),
std::istream_iterator<int>(),
std::ostream_iterator<int>(std::cout, " "));
Both gcc and msvc reject it with (godbolt):
error: no type named 'value_type' in 'using type = struct std::indirectly_readable_traits<std::ostream_iterator<int> >' {aka 'struct std::indirectly_readable_traits<std::ostream_iterator<int> >'}
1436 | && same_as<iter_value_t<_Iter>, iter_value_t<_Out>>)
Surprisingly, the error message here does not show constraints not satisfied
, it just complains that there is no value_type
in indirectly_readable_traits<ostream_iterator<int>>
when instantiating same_as<iter_value_t<_Iter>, iter_value_t<_Out>
.
Why is the above code ill-formed in C++20?
This is a bug in both implementations. Both contain the equivalent of
if constexpr (input_iterator<O> && same_as<iter_value_t<I>, iter_value_t<O>>)
In a constraint this is fine because constraint satisfaction is checked incrementally with short-circuiting (and in any event substitution failure just result in the constraint evaluating to false
), so if O
isn't an input iterator we won't ask about its (possibly non-existent) value type. In an if constexpr
there is no short circuiting, so instead the whole expression is substituted into and is ill-formed on the spot.