Disable class specialization using concepts
You can use type traits to check whether some type is a specialization of span
or std::array
. This works for me:
#include <type_traits>
template<typename, std::ptrdiff_t> class span;
template <typename T>
struct is_array : std::false_type { };
template <typename T, size_t N>
struct is_array<std::array<T, N>> : std::true_type { };
template <typename T>
struct is_span : std::false_type { };
template <typename T, std::ptrdiff_t P>
struct is_span<span<T, P>> : std::true_type { };
template <typename T>
concept bool NotSpanNotArray = !is_array<T>::value && !is_span<T>::value;
template<typename, std::ptrdiff_t> class span {
public:
template<NotSpanNotArray T> constexpr span(T& cont);
// template<NotSpanNotArray T> constexpr span(const T& cont);
};
Working demo: https://wandbox.org/permlink/M0n60U8Hl4mpacuI
Just I am not 100% sure whether such a solution meets that participate in overload resolution if and only if requirement. Some language-lawyer might clarify this.
UPDATE
I just realized that std::is_array
works only for "ordinary" arrays, not std::array
. Therefore I added a custom is_array
type trait as well.
First, create a trait to check for specializations. array
and span
look the same in the sense that they take a type parameter and a non-type parameter:
template <typename T, template <typename, auto> class Z>
struct is_specialization : std::false_type { };
template <typename A, auto V, template <typename, auto> class Z>
struct is_specialization<Z<A,V>, Z> : std::true_type { };
template <typename T, template <typename, auto> class Z>
inline constexpr bool is_specialization_v = is_specialization<T, Z>::value;
And then we can build up a concept from that:
// the last bullet point
template <typename T, typename E>
concept ValidForElement =
ConvertibleTo<std::remove_pointer_t<T>(*)[], E(*)[]>;
template <typename T, typename E>
concept AllowedContainer =
// not a specialization of span (note: requires forward declaration of span)
!is_specialization_v<std::remove_cv_t<T>, std::span>
// not a specialization of array
&& !is_specialization_v<std::remove_cv_t<T>, std::array>
// not a raw array
&& !std::is_array_v<std::remove_cv_t<T>>
&& requires (T cont) {
// data(cont) is well-formed and has a valid type
{ data(cont); } -> ValidForElement<E>
// size(cont) is well-formed
{ size(cont); }
};
Which you would use like:
template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
template <typename C> requires AllowedContainer<C, Element>
span(C&);
template <typename C> requires AllowedContainer<C const, Element>
span(C const&);
};
The const
-ness requirement there prevents the nice partial-concept-id syntax, but we could just add another concept for that I guess:
template <typename T, typename E>
concept ConstAllowedContainer = AllowedContainer<T const, E>;
template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
template <AllowedContainer<E> C> span(C&);
template <ConstAllowedContainer<E> C> span(C const&);
};
Not sure if there's a cleverer approach here yet.
But really this whole pair-of-constructor thing is probably a mistake and you want to do a forwarding reference:
template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
template <AllowedContainer<E> C>
span(C&&);
};
This last approach requires a few tweaks to the concept (all the remove_cv_t
's should become remove_cvref_t
's).