C++概念,用于在写入时具有常量安全性的可读写容器

cclgggtu  于 2023-05-02  发布在  其他
关注(0)|答案(1)|浏览(65)

我想创建一个concept,它允许我为每个readwrite从/到数据容器定义一个函数。数据容器应该有一个.data()和一个.size()方法,以便在阅读/写之前检查大小。另外,对于写函数,数据类型应该标记为const,这样我就不能错误地更改所指向的变量或其内容。
例如,我希望支持像std::vector这样的数据类型,如果可能的话,如果我只将指针和大小分开,则支持std::span
为了检查容器是否同时具有这两种方法,我提出了

template <typename T>
concept HasDataAndSize =
    requires(T t) { t.size(); } && requires(T t) { t.data(); } &&
    requires(T t) { std::is_same<decltype(t.size()), std::size_t>::value; } &&
    requires(T t) { std::is_pointer<decltype(t.data())>::value; };

问题是如果我创建一个函数

template<HasDataAndSize T> void write(const T& data)

该数据对于std::span对象是可写的。
另外,如果我创建一个阅读函数

template<HasDataAndSize T> void read(T read)

std::span可以工作,但是像std::vector这样的容器被复制,并且可以在函数之外访问更改。如果我使用一个T &std::vector可以工作,但我不能从临时的read(std::span{data_ptr, data_size})使用std::span<>
目前我认为我只能允许容器,这不允许改变它里面的ptr,像std::span可以。然而,我的概念仍然让std::span通过,我不知道如何阻止它。
或者有更好的解决方案吗?/为什么我应该使用另一种设计?
我创建了一个小例子来玩:https://godbolt.org/z/nT7jWve91
为了澄清,我写的是一个库,它必须将来自不同数据容器的数据写入文件或将文件中的内容读取到数据中。

nle07wnf

nle07wnf1#

让我们从这个开始:

template <typename T>
concept HasDataAndSize =
    requires(T t) { t.size(); } && requires(T t) { t.data(); } &&
    requires(T t) { std::is_same<decltype(t.size()), std::size_t>::value; } &&
    requires(T t) { std::is_pointer<decltype(t.data())>::value; };

首先也是最重要的,没有必要将所有内容分离到自己的requires-expression中,这增加了大量语法,但没有任何好处。这相当于:

template <typename T>
concept HasDataAndSize =
    requires(T t) {
        t.size();
        t.data();
        std::is_same<decltype(t.size()), std::size_t>::value; // (1)
        std::is_pointer<decltype(t.data())>::value;           // (2)
    };

接下来,上面标记为(1)(2)的行就不会了。做你认为他们会做的事事实上,他们什么都不做。您要检查的是 expressionstd::is_same<T, U>::value是一个有效的表达式--当然是有效的,不管TU是什么。实际上并不是检查类型是否相同。
为了做到这一点,你需要在表达式前面加上requires,或者使用复合需求语法:

template <typename T>
concept HasDataAndSize =
    requires(T t) {
        // t.size() needs to be size_t
        { t.size() } -> std::same_as<size_t>;

        // t.data() needs to be valid and have pointer type
        t.data();
        requires std::is_pointer_v<decltype(t.data())>;
    };

一旦我们到了那里,你还有另一个问题

template<HasDataAndSize T> void write(const T& data)

这是检查T是否有data()size(),但这不是真正相关的问题,因为data不是T,而是T const。在这里,你需要做:

template <class T>
    requires HasDataAndSize<T const>
void write(const T& data)

或引入助手概念:

template <class T> concept HasConstDataAndSize = HasDataAndSize<T const>;
template<HasConstDataAndSize T> void write(const T& data)

或者使用转发引用(这将确保T以对概念有用的方式推导):

template<HasDataAndSize T> void write(T&& data)

最后,这更像是一个API问题,但最好完全避免这个问题,只写:

void write(std::span<std::byte> );
void read(std::span<std::byte const> );

虽然一般来说我并不真正理解这里的API应该做什么。

相关问题