Skip to content

Add checks for extent compatability in span range constructor #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion include/boost/core/span.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,39 @@ struct span_bytes<T, boost::dynamic_extent> {
static constexpr std::size_t value = boost::dynamic_extent;
};

// This is what span_default_constructed_size returns if we can not determine size at compile time,
// or if it is of no value for check(e.g. constexpr std::vector will return 0).
// This value can not be changed since logic only works if it is 0.
static constexpr std::size_t span_unknown_or_useless_size = 0;

// Uses better match betwen short and float argument to do second step in dispatch:
// 1) does type has size method
// 2) is size constexpr and does it return a usable value
template<typename R>
constexpr std::size_t span_default_constructed_size_dispatch(float) {
return span_unknown_or_useless_size;
}

template<typename R, typename std::enable_if<R().size() != span_unknown_or_useless_size, int>::type = 0>
constexpr std::size_t span_default_constructed_size_dispatch(short) {
return R().size();
}

template<typename R, typename std::enable_if<span_has_size<R>::value, int>::type = 0>
constexpr std::size_t span_default_constructed_size() {
return span_default_constructed_size_dispatch<typename std::remove_reference<R>::type>(short{0});
}

template<typename R, typename std::enable_if<!span_has_size<R>::value, int>::type = 0>
constexpr std::size_t span_default_constructed_size() {
return span_unknown_or_useless_size;
}

template<std::size_t E, std::size_t InputE>
constexpr bool static_extents_incompatible() {
return (InputE != span_unknown_or_useless_size) && (E != InputE);
}

} /* detail */

template<class T, std::size_t E>
Expand Down Expand Up @@ -232,7 +265,9 @@ class span {
detail::span_is_range<R, T>::value, int>::type = 0>
explicit constexpr span(R&& r) noexcept(noexcept(boost::data(r)) &&
noexcept(r.size()))
: s_(boost::data(r), r.size()) { }
: s_(boost::data(r), r.size()) {
static_assert(!detail::static_extents_incompatible<E, detail::span_default_constructed_size<R>()>(), "Incompatible Extents");
}

template<class U, std::size_t N,
typename std::enable_if<detail::span_implicit<E, N>::value &&
Expand Down
5 changes: 5 additions & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ run as_bytes_test.cpp ;
run as_writable_bytes_test.cpp ;
compile span_boost_begin_test.cpp ;
run make_span_test.cpp ;
compile-fail span_incompatible_range_size_smaller.cpp ;
compile-fail span_incompatible_range_size_larger.cpp ;
compile span_compatible_range_size.cpp ;
compile span_undetectable_incompatible_range_size.cpp ;
compile span_default_constructed_size.cpp ;

run splitmix64_test.cpp
: : : $(pedantic-errors) ;
Expand Down
18 changes: 18 additions & 0 deletions test/span_compatible_range_size.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <boost/array.hpp>
#include <boost/core/span.hpp>

// use boost::array as generic range since span has no special handling for it,
// unlike for std::array
int main()
{
boost::array<int, 0> arr0{};
boost::span<int, 0> sp0{arr0};
boost::span<const int, 0> csp0{arr0};
(void)sp0;
(void)csp0;
boost::array<int, 4> arr{};
boost::span<int, 4> sp{arr};
boost::span<const int, 4> csp{arr};
(void)sp;
(void)csp;
}
57 changes: 57 additions & 0 deletions test/span_default_constructed_size.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <boost/core/span.hpp>
#include <boost/static_assert.hpp>
#include <thread>
#include <type_traits>
#include <vector>
#if __cplusplus >= 202302L
#include <span>
#endif
using namespace boost;

// well known trick to make size() nicer.
struct MyArray3 {
static constexpr std::integral_constant<std::size_t, 3> size{};
};

// tests for helper used in span range constructor
int main()
{
constexpr auto vec_size = detail::span_default_constructed_size<std::vector<int>>();
BOOST_STATIC_ASSERT(vec_size == 0);
constexpr auto str_size = detail::span_default_constructed_size<std::string>();
BOOST_STATIC_ASSERT(str_size == 0);
constexpr auto arr0_size = detail::span_default_constructed_size<std::array<int, 0>>();
BOOST_STATIC_ASSERT(arr0_size == 0);
constexpr auto arr1_size = detail::span_default_constructed_size<std::array<int, 1>>();
BOOST_STATIC_ASSERT(arr1_size == 1);
constexpr auto arr2_size = detail::span_default_constructed_size<std::array<int, 2>>();
BOOST_STATIC_ASSERT(arr2_size == 2);
constexpr auto non_constexpr_element_arr_size = detail::span_default_constructed_size<std::array<std::thread, 2>>();
BOOST_STATIC_ASSERT(non_constexpr_element_arr_size == 0);

constexpr auto const_arr0_size = detail::span_default_constructed_size<const std::array<int, 0>>();
BOOST_STATIC_ASSERT(const_arr0_size == 0);
constexpr auto const_arr2_size = detail::span_default_constructed_size<const std::array<int, 2>>();
BOOST_STATIC_ASSERT(const_arr2_size == 2);

#ifdef __cpp_lib_integral_constant_callable
constexpr auto my_array3_size = detail::span_default_constructed_size<MyArray3>();
BOOST_STATIC_ASSERT(my_array3_size == 3);
#endif

#if __cplusplus >= 202302L
// static extent span has no default constructor so we can not determine size
constexpr auto span0_size = detail::span_default_constructed_size<std::span<int, 0>>();
BOOST_STATIC_ASSERT(span0_size == 0);
constexpr auto span1_size = detail::span_default_constructed_size<std::span<int, 1>>();
BOOST_STATIC_ASSERT(span1_size == 0);
constexpr auto span2_size = detail::span_default_constructed_size<std::span<int, 2>>();
BOOST_STATIC_ASSERT(span2_size == 0);

constexpr auto const_span2_size = detail::span_default_constructed_size<std::span<const int, 2>>();
BOOST_STATIC_ASSERT(const_span2_size == 0);

constexpr auto dyn_span_size = detail::span_default_constructed_size<std::span<int>>();
BOOST_STATIC_ASSERT(dyn_span_size == 0);
#endif
}
9 changes: 9 additions & 0 deletions test/span_incompatible_range_size_larger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <boost/array.hpp>
#include <boost/core/span.hpp>

int main()
{
boost::array<int, 5> arr{};
boost::span<int, 4> sp{arr};
(void)sp;
}
9 changes: 9 additions & 0 deletions test/span_incompatible_range_size_smaller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <boost/array.hpp>
#include <boost/core/span.hpp>

int main()
{
boost::array<int, 3> arr{};
boost::span<int, 4> sp{arr};
(void)sp;
}
37 changes: 37 additions & 0 deletions test/span_undetectable_incompatible_range_size.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <boost/array.hpp>
#include <boost/core/span.hpp>
#if __cplusplus >= 202302L
#include <span>
#endif
#include <vector>

int main()
{
// many cases where constexpr default constructed container has 0 size, including C++20 onwards
// std::vector so we can not catch that case.
{
boost::array<int, 0> arr{};
boost::span<int, 4> sp{arr};
(void)sp;
std::vector<int> vec(10, 10);
boost::span<int, 4> sp2{vec};
(void)sp2;
}

#if __cplusplus >= 202302L
// fixed extent std::span does not have default constructor so we can not get size
{
std::array<int, 4> arr{};
std::span<int, 4> std_sp{arr};
boost::span<int, 3> sp{std_sp};
(void)sp;
}
// no size check possible for dynamic extent std::span
{
std::array<int, 4> arr{};
std::span<int> std_sp{arr};
boost::span<int, 3> sp{std_sp};
(void)sp;
}
#endif
}