I'm making a simple, non-owning array view class:
template <typename T>
class array_view {
T* data_;
size_t len_;
// ...
};
I want to construct it from any container that has data() and size() member functions, but SFINAE-d correctly such that array_view is only constructible from some container C if it would then be valid and safe behavior to actually traverse data_.
I went with:
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())
{ }
That seems wholly unsatisfying and I'm not even sure it's correct. Am I correctly including all the right containers and excluding all the wrong ones? Is there an easier way to write this requirement?
If we take a look at the proposed
std::experimental::array_viewin N4512, we find the followingViewablerequirement in Table 104:Expression Return type Operational semantics v.size() Convertible to ptrdiff_t v.data() Type T* such that T* is static_cast(v.data()) points to a implicitly convertible to U*, contiguous sequence of at least and is_same_v<remove_cv_t<T>, v.size() objects of (possibly remove_cv_t<U>> is true. cv-qualified) type remove_cv_t<U>.That is, the authors are using essentially the same check for
.data(), but add another one for.size().In order to use pointer arithmetic on
Uby using operations withT, the types need to be similar according to [expr.add]p6. Similarity is defined for qualification conversions, this is why checking for implicit convertibility and then checking similarity (via theis_same) is sufficient for pointer arithmetic.Of course, there's no guarantee for the operational semantics.
In the Standard Library, the only contiguous containers are
std::arrayandstd::vector. There's alsostd::basic_stringwhich has a.data()member, butstd::initializer_listdoes not, despite it being contiguous.All of the
.data()member functions are specified for each individual class, but they all return an actual pointer (no iterator, no proxy).This means that checking for the existence of
.data()is currently sufficient for Standard Library containers; you'd want to add a check for convertibility to makearray_viewless greedy (e.g.array_view<int>rejecting somechar* data()).The implementation can of course be moved away from the interface; you could use Concepts, a concepts emulation, or simply
enable_ifwith an appropriate type function. E.g.And yes, that doesn't follow the usual technique for a type function, but it is shorter and you get the idea.