8

I'm implementing my own version of std::span using Concepts TS. I got stuck implementing these constructors:

template<class Container> constexpr span(Container& cont);
template<class Container> constexpr span(const Container& cont);

Remarks: These constructors shall not participate in overload resolution unless:

  • Container is not a specialization of span, and
  • Container is not a specialization of array

How to implement this using concepts?

  • Can you implement a traits template that answers those questions? Can you convert a traits template into a concept? Does that solve your problem? – Yakk - Adam Nevraumont Jan 16 '19 at 17:05
  • Might have answers [here](https://stackoverflow.com/questions/31762958/check-if-class-is-a-template-specialization)? Good question! – davidbak Jan 16 '19 at 17:11

3 Answers3

2

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.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • "If and only if" was added by another user, I have restored the official wording which is "do not unless". –  Jan 16 '19 at 17:11
  • Isn't the overload resolution thing done by wrapping this in an `enable_if` to make use of SFINAE? – davidbak Jan 16 '19 at 17:12
  • @Lyberta Sorry, I was trying to get rid of the triple negative. I think the correct form would've been "only if", but I believe the intent was clear regardless. – Max Langhof Jan 16 '19 at 17:13
  • 1
    @davidbak not with concepts, this is the right syntax but gets the constraint backwards (needs to be `NotSpanNorArray`) – Caleth Jan 16 '19 at 17:14
  • @Barry updated, thanks. I am now just on my phone, will update demo as well. – Daniel Langr Jan 16 '19 at 17:36
2

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).

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Is it possible to generalize `is_specialization` using variadics? –  Jan 16 '19 at 17:35
  • @Lyberta The problem is you have different _kinds_ of template parameters: a type parameter and a non-type parameter. There's no "any" kind that you could put in there. This is exactly the case that an "anykind" parameter would be super useful. – Barry Jan 16 '19 at 18:01
0

You are miss-using concepts. In a concept world span constructor will look like this with the concept-ts syntax:

struct span{
   span(const ContiguousRange&);
   span(ContiguousRange&);
   span(const span&) =default;
   span(span&) =default;
   };

or with c++20:

struct span{
   span(const ContiguousRange auto&);
   span(ContiguousRange auto&);
   span(const span&) =default;
   span(span&) =default;
   };

Concepts are here to ease abstraction. So that if your interface get more complex when using concepts then you have missed the abstraction. The abstraction here is ContiguousRange (thanks to @Lyberta).

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • `span` requires contiguous memory. AFAIK `Range` doesn't guarantee it. –  Jan 16 '19 at 21:06
  • @Lyberta thank you so I correct and use ContiguousRange instead! http://eel.is/c++draft/range.req#range.refinements-2 – Oliv Jan 16 '19 at 21:08
  • Well, on a practical point, this would require implementing ranges which is a huge amount of work. I only implement my span because it is not yet in the libstdc++ that I use. Maybe using ranges is a better interface, then it should be proposed for standardization. –  Jan 16 '19 at 21:29
  • @Lyberta In this case the concept check you are trying to implement will be unusefull. The reason is that `span` constructor list as described in https://en.cppreference.com/w/cpp/container/span/span already proposes overloads for array specialization and span specialization. Implementing the concept check will be almost unusefull. I say almost because I suppose their is a design or cpp-reference mistakes in this list. Just add a `span(span&)` constructor and a `span(span&)` constructor and the job will be done. – Oliv Jan 16 '19 at 22:03
  • I implemented all 10 constructors that are in the standard. –  Jan 16 '19 at 22:19
  • Except `span` has this additional dynamic/static extent concept, so it needs to treat contiguous ranges that have static extents separately – Barry Jan 16 '19 at 22:25
  • @Barry I would put these constructors where there belong: the specialized class for dynamic or static extent that hold non static data members but that is a detail. – Oliv Jan 16 '19 at 22:32
  • @Lyberta If you implement the concept check you will not follow the standard, ... so why not taking the fast path. – Oliv Jan 16 '19 at 22:34
  • @Lyberta Because the concept check will only exclude `span(container&)` when the argument is a `span<...>` so their is no point in implementing this huge machinery just for that. You define a `span(span<...> a&):span(as_const(a)){}` and the job is done. – Oliv Jan 16 '19 at 22:37