I am trying to extend std::span
to have a bounds checked operator[]
(I am aware gsl::span
provides this)
I have declared my container as follows:
#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) ...) {}
};
This seems to work great, however I noticed that template argument deduction no longer works. E.g.
int main(int argc, char *argv[]) {
BoundsSpan span(argv, argc);
}
gives a "Too few template arguments for class template 'span'" and requires an explicit BoundsSpan<char*>
instead - this is not the case with regular std::span
Additionally, constructing a BoundsSpan
on a C-style array with runtime length gives a "variably modified type cannot be used as a template argument" - did I overlook a template specialization here? An example reproducer would be
void func(int len) {
int arr[len];
BoundsSpan<int> span(arr, len);
}
CodePudding user response:
The std::span template parameter list is not compatible with your variadic parameter list. It takes:
template <typename T, std::size_t N>
class span {...};
However, your class is working in terms of a variadic type list. There is no way for you to propagate the template parameters "up" to the span base when it wants a non-type template parameter, and you only pass types. It's best to try matching the template signature of the span you're impersonating. That brings some simplifications to the problems:
you can expose all of the span constructors as your own (i.e.
using std::span<T, N>::span;
)You can use the same deduction guides that the std::span has, renamed for your class.
Here's a revised version of your code that I found to be functional:
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);
}
};
Then taking std::span's deduction guides for inspiration yields:
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>>>;
With this it should work in your examples and like span. You might want to also wrap the const
version of operator[].