diff --git a/include/boost/core/span.hpp b/include/boost/core/span.hpp index 2e64262d..5fb52997 100644 --- a/include/boost/core/span.hpp +++ b/include/boost/core/span.hpp @@ -153,6 +153,39 @@ struct span_bytes { 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 +constexpr std::size_t span_default_constructed_size_dispatch(float) { + return span_unknown_or_useless_size; +} + +template::type = 0> +constexpr std::size_t span_default_constructed_size_dispatch(short) { + return R().size(); +} + +template::value, int>::type = 0> +constexpr std::size_t span_default_constructed_size() { + return span_default_constructed_size_dispatch::type>(short{0}); +} + +template::value, int>::type = 0> +constexpr std::size_t span_default_constructed_size() { + return span_unknown_or_useless_size; +} + +template +constexpr bool static_extents_incompatible() { + return (InputE != span_unknown_or_useless_size) && (E != InputE); +} + } /* detail */ template @@ -232,7 +265,9 @@ class span { detail::span_is_range::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()>(), "Incompatible Extents"); + } template::value && diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index c8d302f5..1b82970d 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -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) ; diff --git a/test/span_compatible_range_size.cpp b/test/span_compatible_range_size.cpp new file mode 100644 index 00000000..637a0de3 --- /dev/null +++ b/test/span_compatible_range_size.cpp @@ -0,0 +1,18 @@ +#include +#include + +// use boost::array as generic range since span has no special handling for it, +// unlike for std::array +int main() +{ + boost::array arr0{}; + boost::span sp0{arr0}; + boost::span csp0{arr0}; + (void)sp0; + (void)csp0; + boost::array arr{}; + boost::span sp{arr}; + boost::span csp{arr}; + (void)sp; + (void)csp; +} diff --git a/test/span_default_constructed_size.cpp b/test/span_default_constructed_size.cpp new file mode 100644 index 00000000..16afbcf4 --- /dev/null +++ b/test/span_default_constructed_size.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#if __cplusplus >= 202302L +#include +#endif +using namespace boost; + +// well known trick to make size() nicer. +struct MyArray3 { + static constexpr std::integral_constant size{}; +}; + +// tests for helper used in span range constructor +int main() +{ + constexpr auto vec_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(vec_size == 0); + constexpr auto str_size = detail::span_default_constructed_size(); + BOOST_STATIC_ASSERT(str_size == 0); + constexpr auto arr0_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(arr0_size == 0); + constexpr auto arr1_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(arr1_size == 1); + constexpr auto arr2_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(arr2_size == 2); + constexpr auto non_constexpr_element_arr_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(non_constexpr_element_arr_size == 0); + + constexpr auto const_arr0_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(const_arr0_size == 0); + constexpr auto const_arr2_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(const_arr2_size == 2); + +#ifdef __cpp_lib_integral_constant_callable + constexpr auto my_array3_size = detail::span_default_constructed_size(); + 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>(); + BOOST_STATIC_ASSERT(span0_size == 0); + constexpr auto span1_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(span1_size == 0); + constexpr auto span2_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(span2_size == 0); + + constexpr auto const_span2_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(const_span2_size == 0); + + constexpr auto dyn_span_size = detail::span_default_constructed_size>(); + BOOST_STATIC_ASSERT(dyn_span_size == 0); +#endif +} diff --git a/test/span_incompatible_range_size_larger.cpp b/test/span_incompatible_range_size_larger.cpp new file mode 100644 index 00000000..f0a64892 --- /dev/null +++ b/test/span_incompatible_range_size_larger.cpp @@ -0,0 +1,9 @@ +#include +#include + +int main() +{ + boost::array arr{}; + boost::span sp{arr}; + (void)sp; +} diff --git a/test/span_incompatible_range_size_smaller.cpp b/test/span_incompatible_range_size_smaller.cpp new file mode 100644 index 00000000..b0fd37bf --- /dev/null +++ b/test/span_incompatible_range_size_smaller.cpp @@ -0,0 +1,9 @@ +#include +#include + +int main() +{ + boost::array arr{}; + boost::span sp{arr}; + (void)sp; +} diff --git a/test/span_undetectable_incompatible_range_size.cpp b/test/span_undetectable_incompatible_range_size.cpp new file mode 100644 index 00000000..fd0ab223 --- /dev/null +++ b/test/span_undetectable_incompatible_range_size.cpp @@ -0,0 +1,37 @@ +#include +#include +#if __cplusplus >= 202302L +#include +#endif +#include + +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 arr{}; + boost::span sp{arr}; + (void)sp; + std::vector vec(10, 10); + boost::span sp2{vec}; + (void)sp2; + } + +#if __cplusplus >= 202302L + // fixed extent std::span does not have default constructor so we can not get size + { + std::array arr{}; + std::span std_sp{arr}; + boost::span sp{std_sp}; + (void)sp; + } + // no size check possible for dynamic extent std::span + { + std::array arr{}; + std::span std_sp{arr}; + boost::span sp{std_sp}; + (void)sp; + } +#endif +}