Я делаю простой, не владеющий классом вид массива:
template <typename T>
class array_view {
T* data_;
size_t len_;
// ...
};
Я хочу построить его из любого контейнера, который имеет data()
а также size()
функции-члены, но SFINAE-d правильно, что array_view
конструируемый только из некоторого контейнера C
если это будет тогда действительным и безопасным поведением на самом деле пройти data_
,
Я пошел с:
template <typename C,
typename D = decltype(std::declval<C>().data()),
typename = std::enable_if_t<
std::is_convertible<D, T*>::value &&
std::is_same<std::remove_cv_t<T>,
std::remove_cv_t<std::remove_pointer_t<D>>>::value>
>
array_view(C&& container)
: data_(container.data()), len_(container.size())
{ }
Это кажется совершенно неудовлетворительным, и я даже не уверен, что это правильно. Правильно ли я включил все нужные контейнеры и исключил все неправильные? Есть ли более простой способ написать это требование?
Если мы посмотрим на предложенный std::experimental::array_view
в N4512, мы находим следующее Viewable
требование в таблице 104:
Выражение Тип возврата Операционная семантика v.size () Конвертируется в ptrdiff_t v.data () Тип T * такой, что T * является static_cast (v.data ()) указывает на неявно преобразуемый в U *, непрерывная последовательность по крайней мере и is_same_v<remove_cv_t<T>, v.size () объекты (возможно, remove_cv_t<U>> правда. cv-квалифицированный) тип remove_cv_t<U>,
То есть авторы используют по существу одну и ту же проверку для .data()
, но добавьте еще один для .size()
,
Чтобы использовать арифметику указателя на U
используя операции с T
типы должны быть аналогичный согласно [expr.add] p6. сходство определяется для квалификационных преобразований, поэтому проверяется неявная конвертируемость, а затем проверяется сходство (через is_same
) достаточно для арифметики указателей.
Конечно, нет никакой гарантии для операционной семантики.
В стандартной библиотеке единственные смежные контейнеры std::array
а также std::vector
, Есть также std::basic_string
который имеет .data()
член, но std::initializer_list
нет, несмотря на то, что это смежно.
Все .data()
Функции-члены указываются для каждого отдельного класса, но все они возвращают фактический указатель (без итератора, без прокси).
Это означает, что проверка на наличие .data()
в настоящее время достаточно для контейнеров стандартной библиотеки; Вы хотите добавить проверку на конвертируемость, чтобы сделать array_view
менее жадный (например, array_view<int>
отвергая некоторые char* data()
).
Реализация, конечно, может быть удалена от интерфейса; Вы можете использовать Концепции, эмуляцию концепций или просто enable_if
с соответствующей функцией типа. Например.
template<typename T, typename As,
typename size_rt = decltype(std::declval<T>().size())
typename data_rt = decltype(std::declval<T>().data())>
constexpr bool is_viewable =
std::is_convertible_v<size_rt, std::ptrdiff_t>
&& std::is_convertible_v<data_rt, T*>
&& std::is_same_v<std::remove_cv_t<T>, std::remove_cv_t<data_rt>>;
template <typename C,
typename = std::enable_if_t<is_viewable<C, T>>
>
array_view(C&& container)
: data_(container.data()), len_(container.size())
{ }
И да, это не соответствует обычной методике для функции типа, но она короче, и вы поняли идею.
Других решений пока нет …