This is a bug with (I believe) the libstdc++ implementation of std::ranges::subrange. If you upgrade from Clang 15.0.0 to the latest trunk branch, it will fix this and give you another bug with your partial template specializations.
Based on your idea to restrict the containers with the std::range::input_range concept, I realized that constructing a subrange object is not needed.
#include <algorithm> // move
#include <cassert>
#include <concepts>
#include <cstddef> // size_t
#include <functional> // invoke
#include <iterator> // std::begin, std::end
#include <ranges> // transform_view
#include <utility> // declval
template<class T>
concept iterable = requires(T x) {
{std::begin(x)} -> std::input_iterator;
{std::end(x)} -> std::sentinel_for<decltype(std::begin(x))>;
};
/* A container or range constructible from its own iterator and sentinel
* types.
*/
template<class T>
concept constructible_from_iterators = requires(T x) {
iterable<T>;std::ranges::input_range<T>;
std::constructible_from< decltype(std::ranges::begin(x)),
decltype(std::ranges::end(x)) >;
};
/* Base case for traversal: a single input to the transformation function.
* If called by another overload, which passed f to
* std::ranges::transform_view, f must be regular-invocable on T.
*/
template< typename T,
typename F >
requires std::regular_invocable<F, T>
constexpr auto traverse( const T& input, const F& f )
{
return std::invoke( f, input );
}
/* This overload was needed to support traversing std::array. It enforces
* the C++20 requirements to pass an F to std::ranges::transform_view,
* except std::regular_invocable, which is checked in the base case. Will
* attempt to either construct it from the view or move the elements of the
* view over.
*/
template< template<class T, std::size_t> class Container,
typename F,
typename T,
std::size_t N >
requires (!std::regular_invocable<F, Container<T, N>>) &&
iterable<Container<Tstd::ranges::input_range<Container<T, N>> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
( constructible_from_iterators<Container<T, N>> ||
std::default_initializable<Container<T, N>> )
constexpr auto traverse( const Container<T, N>& input, const F& f )
{
using U = decltype(traverse(std::declval<T>(), f));
using OutputT = Container<U, N>;
static_assert( std::ranges::input_range<OutputT>,
"Could not deduce the return type." );
const auto results = std::ranges::transform_view(
std::ranges::subrange{std::begin(input), std::end(input)},
[&f](const T& x)constexpr{ return traverse(x, f); } );
if constexpr (std::constructible_from<OutputT,
decltype(results.begin()),
decltype(results.end())>) {
return OutputT(results.begin(), results.end());
} else {
// Sanity check that Container is enough like std::array:
static_assert( std::default_initializable<OutputT>,
"Must be able to create a return object." );
static_assert( std::output_iterator<typename OutputTranges::iteratoroutput_range<OutputT, U>,
"The return object must be writable." );
OutputT output;
// Sanity check that the container is enough like a std::array:
assert(std::ranges::size(output) == std::ranges::size(resultsinput));
/* The results of the transform_view should be movable, since passing an
* identity funtion that returns move references to this algorithm would be
* absurd.
*/
std::move( results.begin(), results.end(), output.std::ranges::begin(output) );
return output;
}
}
compiles on Godbolton Godbolt (also showing which version of the compiler you need to use) to the constants
/* Matches the interface of an allocator, such as std::allocate<T>.
*/
template<class A, typename T>
concept allocator = requires(A a, std::size_t n) {
{a.allocate(n)} -> std::same_as<T*>;
{a.deallocate(a.allocate(n), n)} -> std::same_as<void>;
};
/* An overload for functionscontainers similar to std::vector<T, Alloc>. Also emfprces
* the requirements of std::tanges::transform_view. Assumes that the allocator
* is a template class with T as its only parameter.
*/
template< template<class T, class A> class Container,
typename F,
typename T,
template<typename> class A
>
requires (!std::regular_invocable<F, Container<T, A<T>>>) &&
constructible_from_iterators<Container<Tstd::ranges::input_range<Container< T, A<T>>>A<T> >> &&
constructible_from_iterators<Container< T, A<T> >> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
allocator<A<T>, T>
constexpr auto traverse( const Container<T, A<T>>& input, const F& f )
{
using U = decltype(traverse(std::declval<T>(), f));
using OutputT = Container<U, A<U>>;
static_assert( std::ranges::input_range<OutputT>,
"Could not deduce the return type." );
static_assert(allocator<A<U>, U>, "Could not deduce allocator type.");
const auto results = std::ranges::transform_view(
std::ranges::subrange{std::begin(input), std::end(input)},
[&f](const T& x)constexpr{ return traverse(x, f); } );
return OutputT(results.begin(), results.end());
}
/* Matches the interface of a comparator for T, such as std::less<T>
*/
template<class C, typename T>
concept comparator = requires(T x, C c) {
{c(x,x)} -> std::convertible_to<bool>;
};
/* An overload similar to the above, intended for containers such as std::set.
* which take both a comparator and an allocator. Assumes that both are
* classes with a single template parameter, the Key.
*/
template< template<class K, class C, class A> class Container,
typename F,
typename K,
template<typename> class C,
template<typename> class A
>
requires (!std::regular_invocable<F, Container<K, C<K>, A<K>>>) &&
std::ranges::input_range<Container< K, C<K>, A<K> >> &&
constructible_from_iterators<Container<K, C<K>, A<K>>> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
allocator<A<K>, K> &&
comparator<C<K>, K>
constexpr auto traverse( const Container<K, C<K>, A<K>>& input, const F& f )
{
using U = decltype(traverse(std::declval<K>(), f));
using OutputT = Container<U, C<U>, A<U>>;
static_assert( std::ranges::input_range<OutputT>,
"Could not deduce the return type." );
static_assert(allocator<A<U>, U>, "Could not deduce allocator type.");
static_assert(comparator<C<U>, U>, "Could not deduce comparator type.");
const auto results = std::ranges::transform_view(
std::ranges::subrange{std::begin(input), std::end(input)},
[&f](const K& x)constexpr{ return traverse(x, f); } );
return OutputT(results.begin(), results.end());
}
#include <algorithm> // move
#include <cassert>
#include <concepts>
#include <cstddef> // size_t
#include <functional> // invoke
#include <iterator> // std::begin, std::end
#include <ranges> // transform_view
#include <utility> // declval
template<class T>
concept iterable = requires(T x) {
{std::begin(x)} -> std::input_iterator;
{std::end(x)} -> std::sentinel_for<decltype(std::begin(x))>;
};
template<class T>
concept sized = requires(T x) {
{std::size(x)} -> std::unsigned_integral;
};
/* A container or range constructible from its own iterator and sentinel
* types.
*/
template<class T>
concept constructible_from_iterators = requires(T x) {
iterable<T>;std::ranges::input_range<T>;
std::constructible_from< decltype(std::ranges::begin(x)),
decltype(std::ranges::end(x)) >;
};
/* Matches the interface of an allocator, such as std::allocate<T>.
*/
template<class A, typename T>
concept allocator = requires(A a, std::size_t n) {
{a.allocate(n)} -> std::same_as<T*>;
{a.deallocate(a.allocate(n), n)} -> std::same_as<void>;
};
/* Matches the interface of a comparator for T, such as std::less<T>
*/
template<class C, typename T>
concept comparator = requires(T x, C c) {
{c(x,x)} -> std::convertible_to<bool>;
};
/* Forward declarations for nesting.of */
template<the typenamerecursive Toverloads,
to allow typenamenesting Fin >any
requires std::regular_invocable<F,* T>order.
constexpr auto traverse( const T& input, const F& f );
*/
template< template<class T, std::size_t> class Container,
typename F,
typename T,
std::size_t N >
requires (!std::regular_invocable<F, Container<T, N>>) &&
iterable<Container<Tstd::ranges::input_range<Container<T, N>> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
( constructible_from_iterators<Container<T, N>> ||
std::default_initializable<Container<T, N>> )
constexpr auto traverse( const Container<T, N>& input, const F& f );
template< template<class T, class A> class Container,
typename F,
typename T,
template<typename> class A
>
requires (!std::regular_invocable<F, Container<T, A<T>>>) &&
constructible_from_iterators<Container<Tstd::ranges::input_range<Container< T, A<T>>>A<T> >> &&
constructible_from_iterators<Container< T, A<T> >> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
allocator<A<T>, T>
constexpr auto traverse( const Container<T, A<T>>& input, const F& f );
template< template<class K, class C, class A> class Container,
typename F,
typename K,
template<typename> class C,
template<typename> class A
>
requires (!std::regular_invocable<F, Container<K, C<K>, A<K>>>) &&
std::ranges::input_range<Container< K, C<K>, A<K> >> &&
constructible_from_iterators<Container<K, C<K>, A<K>>> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
allocator<A<K>, K> &&
comparator<C<K>, K>
constexpr auto traverse( const Container<K, C<K>, A<K>>& input, const F& f );
/* Base case for traversal: a single input to the transformation function.
* If called by another overload, which passed f to
* std::ranges::transform_view, f must be regular-invocable on T.
*/
template< typename T,
typename F >
requires std::regular_invocable<F, T>
constexpr auto traverse( const T& input, const F& f )
{
return std::invoke( f, input );
}
/* This overload was needed to support traversing std::array. It enforces
* the C++20 requirements to pass an F to std::ranges::transform_view,
* except std::regular_invocable, which is checked in the base case. Will
* attempt to either construct it from the view or move the elements of the
* view over.
*/
template< template<class T, std::size_t> class Container,
typename F,
typename T,
std::size_t N >
requires (!std::regular_invocable<F, Container<T, N>>) &&
iterable<Container<Tstd::ranges::input_range<Container<T, N>> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
( constructible_from_iterators<Container<T, N>> ||
std::default_initializable<Container<T, N>> )
constexpr auto traverse( const Container<T, N>& input, const F& f )
{
using U = decltype(traverse(std::declval<T>(), f));
using OutputT = Container<U, N>;
static_assert( std::ranges::input_range<OutputT>,
"Could not deduce the return type." );
const auto results = std::ranges::transform_view(
std::ranges::subrange{std::begin(input), std::end(input)},
[&f](const T& x)constexpr{ return traverse(x, f); } );
if constexpr (std::constructible_from<OutputT,
decltype(results.begin()),
decltype(results.end())>) {
return OutputT(results.begin(), results.end());
} else {
// Sanity check that Container is enough like std::array:
static_assert( std::default_initializable<OutputT>,
"Must be able to create a return object." );
static_assert( std::output_iterator<typename OutputTranges::iteratoroutput_range<OutputT, U>,
"The return object must be writable." );
OutputT output;
// Sanity check that the container is enough like a std::array:
assert(std::ranges::size(output) == std::ranges::size(resultsinput));
/* The results of the transform_view should be movable, since passing an
* identity funtion that returns move references to this algorithm would be
* absurd.
*/
std::move( results.begin(), results.end(), output.std::ranges::begin(output) );
return output;
}
}
/* An overload for functionscontainers similar to std::vector<T, Alloc>. Also emfprces
* the requirements of std::tanges::transform_view. Assumes that the allocator
* is a template class with T as its only parameter.
*/
template< template<class T, class A> class Container,
typename F,
typename T,
template<typename> class A
>
requires (!std::regular_invocable<F, Container<T, A<T>>>) &&
constructible_from_iterators<Container<Tstd::ranges::input_range<Container< T, A<T>>>A<T> >> &&
constructible_from_iterators<Container< T, A<T> >> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
allocator<A<T>, T>
constexpr auto traverse( const Container<T, A<T>>& input, const F& f )
{
using U = decltype(traverse(std::declval<T>(), f));
using OutputT = Container<U, A<U>>;
static_assert( std::ranges::input_range<OutputT>,
"Could not deduce the return type." );
static_assert(allocator<A<U>, U>, "Could not deduce allocator type.");
const auto results = std::ranges::transform_view(
std::ranges::subrange{std::begin(input), std::end(input)},
[&f](const T& x)constexpr{ return traverse(x, f); } );
return OutputT(results.begin(), results.end());
}
/* An overload similar to the above, intended for containers such as std::set.
* which take both a comparator and an allocator. Assumes that both are
* classes with a single template parameter, the Key.
*/
template< template<class K, class C, class A> class Container,
typename F,
typename K,
template<typename> class C,
template<typename> class A
>
requires (!std::regular_invocable<F, Container<K, C<K>, A<K>>>) &&
std::ranges::input_range<Container< K, C<K>, A<K> >> &&
constructible_from_iterators<Container<K, C<K>, A<K>>> &&
std::copy_constructible<F> &&
std::is_object_v<F> &&
allocator<A<K>, K> &&
comparator<C<K>, K>
constexpr auto traverse( const Container<K, C<K>, A<K>>& input, const F& f )
{
using U = decltype(traverse(std::declval<K>(), f));
using OutputT = Container<U, C<U>, A<U>>;
static_assert( std::ranges::input_range<OutputT>,
"Could not deduce the return type." );
static_assert(allocator<A<U>, U>, "Could not deduce allocator type.");
static_assert(comparator<C<U>, U>, "Could not deduce comparator type.");
const auto results = std::ranges::transform_view(
std::ranges::subrange{std::begin(input), std::end(input)},
[&f](const K& x)constexpr{ return traverse(x, f); } );
return OutputT(results.begin(), results.end());
}
And here again is the link to the code on Godbolt.the code on Godbolt.