SFINAE 将任何容器转换为 C 样式数组视图

SFINAE-ing any container into a c-style array view

我正在制作一个简单的非拥有数组视图 class:

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())
{ }

这似乎完全不能令人满意,我什至不确定它是否正确。我是否正确地包含了所有正确的容器并排除了所有错误的容器?有没有更简单的方法来编写这个需求?

如果我们查看 N4512 中提议的 std::experimental::array_view,我们会在 Table 104 中找到以下 Viewable 要求:

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

也就是说,作者对 .data() 使用基本相同的检查,但为 .size() 添加另一个检查。

为了通过对 T 的操作在 U 上使用指针算法,根据 [expr.add,类型需要 相似 ]]p6。 相似性 是为资格转换定义的,这就是为什么检查隐式可转换性然后检查相似性(通过 is_same)足以进行指针运算的原因。

当然,不能保证操作语义。


在标准库中,唯一连续的容器是 std::arraystd::vector。还有 std::basic_string 有一个 .data() 成员,但是 std::initializer_list 没有,尽管它是连续的。

所有 .data() 成员函数都是为每个个体指定的 class,但它们都是 return 实际指针(无迭代器,无代理)。

这意味着检查 .data() 的存在对于标准库容器来说已经足够了;你想添加一个可转换性检查以使 array_view 不那么贪婪(例如 array_view<int> 拒绝一些 char* data())。


实现当然可以从接口中移开;你可以使用 Concepts,一个概念模拟,或者简单地使用 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())
{ }

是的,这并没有遵循类型函数的常用技术,但它更短,您明白了。