I would like to use std::ranges::iota_view
for a custom data type that is "int" like in the same way I would use std::ranges::iota_view
for an int
. I couldn't figure out how to adjust the code based on gcc's error messages, which are posted below:
#include <ranges>
#include <limits>
struct IntLike {
IntLike(int val = {}) : m_val{val} {}
operator int() const { return m_val; }
IntLike& operator++() { ++m_val; return *this; }
IntLike operator++(int) { IntLike ret = *this; ++m_val; return ret; }
friend auto operator <=>(IntLike lhs, IntLike rhs) = default;
int m_val;
};
namespace std {
template<>
struct iterator_traits<IntLike> {
using difference_type = IntLike;
};
}
int main()
{
static_assert(std::same_as<std::iter_difference_t<int>, int>);
static_assert(std::same_as<std::iter_difference_t<IntLike>, IntLike>);
std::ranges::iota_view(0, 2);
std::ranges::iota_view(IntLike{0}, IntLike{2});
return 0;
}
Errors:
<source>:26:50: error: class template argument deduction failed:
26 | std::ranges::iota_view(IntLike{0}, IntLike{2});
| ^
<source>:26:50: error: no matching function for call to 'iota_view(IntLike, IntLike)'
In file included from <source>:1:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:617:7: note: candidate: 'template<class _Winc, class _Bound> iota_view(typename std::ranges::iota_view<_Winc, _Bound>::_Iterator, typename std::ranges::iota_view<_Winc, _Bound>::_Sentinel)-> std::ranges::iota_view<_Winc, _Bound> requires !(same_as<_Winc, _Bound>) && !(same_as<_Bound, std::unreachable_sentinel_t>)'
617 | iota_view(_Iterator __first, _Sentinel __last)
| ^~~~~~~~~
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:617:7: note: template argument deduction/substitution failed:
<source>:26:50: note: couldn't deduce template parameter '_Winc'
This is non-trivial and, even when it works, it is highly limited.
The first problem is the constructor. The constructor you are trying to use is the one that takes the starting point and the boundary condition. However, this constructor is deliberately prevented from using class template argument deduction through the use of
std::type_identity_t
. Instead, an explicit deduction guide is used.However, this guide requires that the types in question are "integer-like". And it is not possible for user-code to create a type that is "integer-like"; only implementations can create classes that are "integer-like". So this deduction guide never applies.
That's why the compiler thinks you're trying to pass a pair of iterators.
Now, you can bypass the deduction guide by specifying the types directly (though you'll never be able to use
views::iota
, since it always uses the guides). But even if you do, your type must conform to a bunch of concepts: weakly_incrementable, __WeaklyEqualityComparableWith, advanceable&decrementable, and probably a few others. You'll also need to provide an iota-diff-t for your type.Overall... it's probably not worth the effort. It would be a much better use of your time to take a regular
iota
range and apply aviews::transform
to convert each integer to the actual type you desire. Likeviews::iota(...) | views::transform([](int i) { return IntLike(i);})
.