Отключить специализацию класса, используя понятия

Я реализую свою собственную версию std::span используя Концепции TS. Я застрял в реализации этих конструкторов:

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

Примечания: Эти конструкторы не должны участвовать в разрешении перегрузки, если:

  • Container это не специализация span, а также
  • Container это не специализация array

Как реализовать это с помощью концепций?

3 ответа

Решение

Сначала создайте признак для проверки специализаций. array а также span выглядят одинаково в том смысле, что они принимают параметр типа и параметр не тип:

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;

И тогда мы можем построить концепцию из этого:

// 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); }
    };

Который вы бы использовали как:

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&);
};

const требование -ness препятствует хорошему синтаксису частично-концептуального идентификатора, но мы могли бы просто добавить другую концепцию для этого:

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&);
};

Не уверен, что здесь есть более умный подход.


Но на самом деле вся эта пара конструкторов, вероятно, является ошибкой, и вы хотите сделать ссылку на пересылку:

template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
    template <AllowedContainer<E> C>
    span(C&&);
};

Этот последний подход требует нескольких изменений в концепции (все remove_cv_t должно стать remove_cvref_t "S).

Вы можете использовать черты типа, чтобы проверить, является ли какой-то тип специализацией span или же std::array, Это работает для меня:

#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);
};

Рабочая демонстрация: https://wandbox.org/permlink/M0n60U8Hl4mpacuI

Просто я не уверен на 100%, соответствует ли такое решение участию в разрешении перегрузки тогда и только тогда, когда это требуется. Некоторые юристы могут прояснить это.


ОБНОВИТЬ

Я только что понял, что std::is_array работает только для "обычных" массивов, а не std::array, Поэтому я добавил кастом is_array черта типа также.

Вы не используете понятия. В концептуальном мире конструктор span будет выглядеть так с синтаксисом concept-ts:

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

или с с ++20:

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

Концепции здесь для облегчения абстракции. Так что если ваш интерфейс становится более сложным при использовании концепций, то вы пропустили абстракцию. Абстракция здесь ContiguousRange (спасибо @Lyberta).

Другие вопросы по тегам