首页 > 解决方案 > 扩展 std::span 时模板参数推导的问题

问题描述

我正在尝试扩展std::span以检查边界operator[](我知道gsl::span提供了这个)

我已将我的容器声明如下:

#include <span>
#include <string>
#include <utility>
#include <stdexcept>

template <typename ... TopArgs> class BoundsSpan : private std::span<TopArgs...> {
public:
    typename std::span<TopArgs...>::reference operator[](std::size_t idx) const {
        if (idx >= this->size()) [[unlikely]] {
            throw std::out_of_range(std::string("span out of bounds access detected - wanted index [" + std::to_string(idx) + "] but size is " +
                                                std::to_string(this->size())));
        }
        return std::span<TopArgs...>::operator[](idx);
    }
    template<typename ... Args>
    BoundsSpan(Args&& ... args) : std::span<TopArgs...>(std::forward<Args>(args) ...) {}
};

这似乎很好用,但是我注意到模板参数推导不再有效。例如

int main(int argc, char *argv[]) {
    BoundsSpan span(argv, argc);
}

给出“类模板'span'的模板参数太少”并需要显式BoundsSpan<char*>代替 - 这不是常规的情况std::span

此外,BoundsSpan在具有运行时长度的 C 样式数组上构造 a 会给出“不能将可变修改的类型用作模板参数”——我是否在这里忽略了模板特化?一个示例复制器是

void func(int len) {
    int arr[len];
    BoundsSpan<int> span(arr, len);
}

标签: c++templates

解决方案


std::span 模板参数列表与您的可变参数列表不兼容。它需要:

template <typename T, std::size_t N>
class span {...};

但是,您的课程是根据可变参数类型列表工作的。当需要非类型模板参数时,您无法将模板参数“向上”传播到跨度基础,并且您只能传递类型。最好尝试匹配您正在模拟的跨度的模板签名。这为问题带来了一些简化:

  • 您可以将所有跨度构造函数公开为您自己的(即using std::span<T, N>::span;

  • 您可以使用与 std::span 相同的演绎指南,为您的班级重命名。

这是您的代码的修订版本,我发现它可以正常工作:

template <class T, std::size_t N = std::dynamic_extent> 
class BoundsSpan : private std::span<T, N> {
public:
    using std::span<T, N>::span;
    using std::span<T, N>::data;
    using std::span<T, N>::size;
    // .. and all the other interface functions

    typename std::span<T, N>::reference operator[](std::size_t idx) const {
        if (idx >= this->size()) [[unlikely]] {
            throw std::out_of_range("span out of bounds - wanted index [" + 
                std::to_string(idx) + "] but size is " + std::to_string(size()));
        }
        return std::span<T, N>::operator[](idx);
    }
};

然后使用 std::span 的推理指南来获得灵感:

template <class It, class EndOrSize>
BoundsSpan(It, EndOrSize) -> BoundsSpan<std::remove_reference_t<std::iter_reference_t<It>>>;
        
template<class T, std::size_t N>
BoundsSpan(T (&)[N]) -> BoundsSpan<T, N>;
        
template<class T, std::size_t N>
BoundsSpan(std::array<T, N>&) -> BoundsSpan<T, N>;
        
template<class T, std::size_t N>
BoundsSpan(const std::array<T, N>&) -> BoundsSpan<const T, N>;
        
template<class R>
BoundsSpan(R&&) -> 
    BoundsSpan<std::remove_reference_t<std::ranges::range_reference_t<R>>>;

有了这个,它应该可以在你的例子中工作,就像跨度一样。您可能还想包装constoperator[] 的版本。

https://godbolt.org/z/7ec3PYe7e


推荐阅读