Propagate T::index_type through partial_range
When partial_range is used on a container that defines index_type, define that same index_type on the partial_range. This allows other callers, such as enumerate(), to maintain the correct index type.
This commit is contained in:
parent
16aa554c0e
commit
b6bebcf6b8
|
@ -6,5 +6,5 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
template <typename I>
|
||||
template <typename range_iterator, typename range_index_type = void>
|
||||
class partial_range_t;
|
||||
|
|
|
@ -48,13 +48,24 @@ namespace partial_range_detail
|
|||
{
|
||||
|
||||
#define REPORT_FORMAT_STRING "%s:%u: %s %lu past %p end %lu \"%s\""
|
||||
/* Round reporting into large buckets. Code size is more
|
||||
* important than stack space on a cold path.
|
||||
/* Given the length of a filename (in `NF`) and the length of an
|
||||
* expression to report (in `NE`), compute the required size of a buffer
|
||||
* that can hold the result of applying REPORT_FORMAT_STRING. This
|
||||
* overestimates the size since sizeof(REPORT_FORMAT_STRING) is used
|
||||
* without accounting for the %-escapes being replaced with the expanded
|
||||
* text, and all expansions are assumed to be of maximum length.
|
||||
*
|
||||
* Round reporting into large buckets. Code size is more important than
|
||||
* stack space on a cold path.
|
||||
*/
|
||||
template <std::size_t NF, std::size_t NE>
|
||||
constexpr std::size_t required_buffer_size = ((sizeof(REPORT_FORMAT_STRING) + sizeof("65535") + (sizeof("18446744073709551615") * 2) + sizeof("0x0000000000000000") + (NF + NE + sizeof("begin"))) | 0xff) + 1;
|
||||
|
||||
template <std::size_t N>
|
||||
/* This is only used on a path that will throw an exception, so mark
|
||||
* it as cold. In a program with no bugs, which is given no
|
||||
* ill-formed data on input, these exceptions never happen.
|
||||
*/
|
||||
__attribute_cold
|
||||
void prepare_error_string(std::array<char, N> &buf, unsigned long d, const char *estr, const char *file, unsigned line, const char *desc, unsigned long expr, const uintptr_t t)
|
||||
{
|
||||
|
@ -81,18 +92,36 @@ inline auto adl_end(T &t)
|
|||
return end(t);
|
||||
}
|
||||
|
||||
template <typename>
|
||||
void range_index_type(...);
|
||||
|
||||
template <
|
||||
typename range_type,
|
||||
/* If `range_type::index_type` is not defined, fail.
|
||||
* If `range_type::index_type` is void, fail.
|
||||
*/
|
||||
typename index_type = typename std::remove_reference<typename std::remove_reference<range_type>::type::index_type &>::type,
|
||||
/* If `range_type::index_type` is not a suitable argument to
|
||||
* range_type::operator[](), fail.
|
||||
*/
|
||||
typename = decltype(std::declval<range_type &>().operator[](std::declval<index_type>()))
|
||||
>
|
||||
index_type range_index_type(std::nullptr_t);
|
||||
|
||||
}
|
||||
|
||||
#if DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
|
||||
struct partial_range_error;
|
||||
#endif
|
||||
|
||||
template <typename I>
|
||||
template <typename range_iterator, typename range_index_type>
|
||||
class partial_range_t
|
||||
{
|
||||
public:
|
||||
static_assert(!std::is_reference<range_iterator>::value);
|
||||
using range_owns_iterated_storage = std::false_type;
|
||||
typedef I iterator;
|
||||
using iterator = range_iterator;
|
||||
using index_type = range_index_type;
|
||||
/* When using the unminimized type, forward declare a structure.
|
||||
*
|
||||
* When using the minimized type, add a typedef here so that later
|
||||
|
@ -161,7 +190,7 @@ public:
|
|||
return std::reverse_iterator<iterator>{m_begin};
|
||||
}
|
||||
[[nodiscard]]
|
||||
partial_range_t<std::reverse_iterator<iterator>> reversed() const
|
||||
partial_range_t<std::reverse_iterator<iterator>, index_type> reversed() const
|
||||
{
|
||||
return {rbegin(), rend()};
|
||||
}
|
||||
|
@ -170,8 +199,8 @@ public:
|
|||
#if DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
|
||||
struct partial_range_error
|
||||
#else
|
||||
template <typename I>
|
||||
struct partial_range_t<I>::partial_range_error
|
||||
template <typename range_iterator, typename range_index_type>
|
||||
struct partial_range_t<range_iterator, range_index_type>::partial_range_error
|
||||
#endif
|
||||
final : std::out_of_range
|
||||
{
|
||||
|
@ -191,6 +220,7 @@ namespace partial_range_detail
|
|||
{
|
||||
|
||||
template <typename range_exception, std::size_t required_buffer_size>
|
||||
__attribute_always_inline()
|
||||
inline void check_range_bounds(const char *file, unsigned line, const char *estr, const uintptr_t t, const std::size_t index_begin, const std::size_t index_end, const std::size_t d)
|
||||
{
|
||||
#ifdef DXX_CONSTANT_TRUE
|
||||
|
@ -233,6 +263,7 @@ inline void check_range_bounds(const char *file, unsigned line, const char *estr
|
|||
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
template <typename range_exception, std::size_t required_buffer_size, typename P>
|
||||
__attribute_always_inline()
|
||||
inline void check_range_object_size(const char *file, unsigned line, const char *estr, P &ref, const std::size_t index_begin, const std::size_t index_end)
|
||||
{
|
||||
const auto ptr = std::addressof(ref);
|
||||
|
@ -268,10 +299,11 @@ template <
|
|||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
std::size_t required_buffer_size,
|
||||
#endif
|
||||
typename index_type,
|
||||
typename iterator_type
|
||||
>
|
||||
[[nodiscard]]
|
||||
inline partial_range_t<iterator_type> unchecked_partial_range_advance(
|
||||
inline partial_range_t<iterator_type, index_type> unchecked_partial_range_advance(
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
const char *const file, const unsigned line, const char *const estr,
|
||||
#endif
|
||||
|
@ -290,7 +322,7 @@ inline partial_range_t<iterator_type> unchecked_partial_range_advance(
|
|||
/* Avoid iterator dereference if range is empty */
|
||||
if (index_end)
|
||||
{
|
||||
partial_range_detail::check_range_object_size<typename partial_range_t<iterator_type>::partial_range_error, required_buffer_size>(file, line, estr, *range_begin, index_begin, index_end);
|
||||
partial_range_detail::check_range_object_size<typename partial_range_t<iterator_type, index_type>::partial_range_error, required_buffer_size>(file, line, estr, *range_begin, index_begin, index_end);
|
||||
}
|
||||
#endif
|
||||
auto range_end = range_begin;
|
||||
|
@ -310,29 +342,37 @@ template <
|
|||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
std::size_t required_buffer_size,
|
||||
#endif
|
||||
typename I,
|
||||
typename range_type,
|
||||
typename index_begin_type,
|
||||
typename index_end_type
|
||||
typename index_end_type,
|
||||
typename iterator_type = decltype(std::begin(std::declval<range_type &>())),
|
||||
/* This is in the template signature so that an `iterator_type`
|
||||
* which does not provide `operator*()` will trigger an error
|
||||
* and remove this overload from the resolution set.
|
||||
*/
|
||||
typename reference = decltype(*std::declval<iterator_type>())
|
||||
>
|
||||
[[nodiscard]]
|
||||
__attribute_always_inline()
|
||||
inline auto (unchecked_partial_range)(
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
const char *const file, const unsigned line, const char *const estr,
|
||||
#endif
|
||||
const I range_begin, const index_begin_type &index_begin, const index_end_type &index_end)
|
||||
range_type &range, const index_begin_type &index_begin, const index_end_type &index_end)
|
||||
{
|
||||
/* Require unsigned length */
|
||||
static_assert(std::is_unsigned<index_begin_type>::value, "offset to partial_range must be unsigned");
|
||||
static_assert(std::is_unsigned<index_end_type>::value, "length to partial_range must be unsigned");
|
||||
static_assert(!std::is_void<reference>::value, "dereference of iterator must not be void");
|
||||
return unchecked_partial_range_advance<
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
required_buffer_size,
|
||||
#endif
|
||||
I>(
|
||||
decltype(partial_range_detail::range_index_type<range_type>(nullptr)), iterator_type>(
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
file, line, estr,
|
||||
#endif
|
||||
range_begin, index_begin, index_end
|
||||
std::begin(range), index_begin, index_end
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -340,7 +380,57 @@ template <
|
|||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
std::size_t required_buffer_size,
|
||||
#endif
|
||||
typename I,
|
||||
typename iterator_type,
|
||||
typename index_begin_type,
|
||||
typename index_end_type,
|
||||
/* C arrays (`int a[5];`) can match both the overload that calls
|
||||
* `std::begin(range)` and the overload that calls
|
||||
* `operator*(range)`, leading to an ambiguity. Some supporting
|
||||
* libraries define C arrays, which callers use partial_range on, so
|
||||
* the array use cannot be converted to std::array. Use
|
||||
* std::enable_if to disable this overload in the case of a C array.
|
||||
*
|
||||
* C++ std::array does not permit `operator*(range)`, and so does
|
||||
* not match this overload, regardless of whether std::enable_if is
|
||||
* used.
|
||||
*/
|
||||
typename reference = typename std::enable_if<!std::is_array<iterator_type>::value, decltype(*std::declval<iterator_type>())>::type
|
||||
>
|
||||
[[nodiscard]]
|
||||
inline auto (unchecked_partial_range)(
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
const char *const file, const unsigned line, const char *const estr,
|
||||
#endif
|
||||
iterator_type iterator, const index_begin_type &index_begin, const index_end_type &index_end)
|
||||
{
|
||||
/* Require unsigned length */
|
||||
static_assert(std::is_unsigned<index_begin_type>::value, "offset to partial_range must be unsigned");
|
||||
static_assert(std::is_unsigned<index_end_type>::value, "length to partial_range must be unsigned");
|
||||
static_assert(!std::is_void<reference>::value, "dereference of iterator must not be void");
|
||||
/* Do not try to guess an index_type when supplied an iterator. Use
|
||||
* `void` to state that no index_type is available. Callers which
|
||||
* need a defined `index_type` should use the range-based
|
||||
* unchecked_partial_range() instead, which can extract an
|
||||
* index_type from the range_type.
|
||||
*/
|
||||
return unchecked_partial_range_advance<
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
required_buffer_size,
|
||||
#endif
|
||||
void, iterator_type>(
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
file, line, estr,
|
||||
#endif
|
||||
std::move(iterator), index_begin, index_end
|
||||
);
|
||||
}
|
||||
|
||||
/* Take either a range or an iterator, and forward it to an appropriate
|
||||
* function, adding an index_begin={} along the way.
|
||||
*/
|
||||
template <
|
||||
std::size_t required_buffer_size,
|
||||
typename iterable,
|
||||
typename index_end_type
|
||||
>
|
||||
[[nodiscard]]
|
||||
|
@ -348,80 +438,86 @@ inline auto (unchecked_partial_range)(
|
|||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
const char *const file, const unsigned line, const char *const estr,
|
||||
#endif
|
||||
const I range_begin, const index_end_type &index_end)
|
||||
iterable &&it, const index_end_type &index_end)
|
||||
{
|
||||
return unchecked_partial_range<
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
required_buffer_size,
|
||||
required_buffer_size
|
||||
#endif
|
||||
I, index_end_type, index_end_type>(
|
||||
>(
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
file, line, estr,
|
||||
#endif
|
||||
range_begin, index_end_type{}, index_end
|
||||
std::forward<iterable>(it), index_end_type{}, index_end
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
std::size_t required_buffer_size,
|
||||
typename T,
|
||||
typename range_type,
|
||||
typename index_begin_type,
|
||||
typename index_end_type,
|
||||
typename I = decltype(std::begin(std::declval<T &>()))
|
||||
typename iterator_type = decltype(std::begin(std::declval<range_type &>()))
|
||||
>
|
||||
[[nodiscard]]
|
||||
inline auto (partial_range)(const char *const file, const unsigned line, const char *const estr, T &t, const index_begin_type &index_begin, const index_end_type &index_end)
|
||||
inline auto (partial_range)(const char *const file, const unsigned line, const char *const estr, range_type &range, const index_begin_type &index_begin, const index_end_type &index_end)
|
||||
{
|
||||
partial_range_detail::check_range_bounds<typename partial_range_t<I>::partial_range_error, required_buffer_size>(file, line, estr, reinterpret_cast<uintptr_t>(std::addressof(t)), index_begin, index_end, std::size(t));
|
||||
partial_range_detail::check_range_bounds<
|
||||
typename partial_range_t<iterator_type, decltype(partial_range_detail::range_index_type<range_type>(nullptr))>::partial_range_error,
|
||||
required_buffer_size
|
||||
>(file, line, estr, reinterpret_cast<uintptr_t>(std::addressof(range)), index_begin, index_end, std::size(range));
|
||||
return unchecked_partial_range<
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
required_buffer_size,
|
||||
#endif
|
||||
I, index_begin_type, index_end_type>(
|
||||
range_type, index_begin_type, index_end_type>(
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
file, line, estr,
|
||||
#endif
|
||||
std::begin(t), index_begin, index_end
|
||||
range, index_begin, index_end
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
std::size_t required_buffer_size,
|
||||
typename T,
|
||||
typename range_type,
|
||||
typename index_end_type
|
||||
>
|
||||
[[nodiscard]]
|
||||
inline auto (partial_range)(const char *const file, const unsigned line, const char *const estr, T &t, const index_end_type &index_end)
|
||||
inline auto (partial_range)(const char *const file, const unsigned line, const char *const estr, range_type &range, const index_end_type &index_end)
|
||||
{
|
||||
return partial_range<required_buffer_size, T, index_end_type, index_end_type>(file, line, estr, t, index_end_type{}, index_end);
|
||||
return partial_range<required_buffer_size, range_type, index_end_type, index_end_type>(file, line, estr, range, index_end_type{}, index_end);
|
||||
}
|
||||
|
||||
template <
|
||||
std::size_t required_buffer_size,
|
||||
typename T,
|
||||
typename range_type,
|
||||
typename index_begin_type,
|
||||
typename index_end_type
|
||||
>
|
||||
[[nodiscard]]
|
||||
inline auto (partial_const_range)(const char *const file, const unsigned line, const char *const estr, const T &t, const index_begin_type &index_begin, const index_end_type &index_end)
|
||||
inline auto (partial_const_range)(const char *const file, const unsigned line, const char *const estr, const range_type &range, const index_begin_type &index_begin, const index_end_type &index_end)
|
||||
{
|
||||
return partial_range<required_buffer_size, const T, index_begin_type, index_end_type>(file, line, estr, t, index_begin, index_end);
|
||||
return partial_range<required_buffer_size, const range_type, index_begin_type, index_end_type>(file, line, estr, range, index_begin, index_end);
|
||||
}
|
||||
|
||||
template <
|
||||
std::size_t required_buffer_size,
|
||||
typename T,
|
||||
typename range_type,
|
||||
typename index_end_type
|
||||
>
|
||||
[[nodiscard]]
|
||||
inline auto (partial_const_range)(const char *const file, const unsigned line, const char *const estr, const T &t, const index_end_type &index_end)
|
||||
inline auto (partial_const_range)(const char *const file, const unsigned line, const char *const estr, const range_type &range, const index_end_type &index_end)
|
||||
{
|
||||
return partial_range<required_buffer_size, const T, index_end_type>(file, line, estr, t, index_end);
|
||||
return partial_range<required_buffer_size, const range_type, index_end_type>(file, line, estr, range, index_end);
|
||||
}
|
||||
|
||||
template <typename T, typename I = decltype(std::begin(std::declval<T &>()))>
|
||||
template <typename range_type>
|
||||
[[nodiscard]]
|
||||
inline partial_range_t<I> (make_range)(T &t)
|
||||
inline partial_range_t<
|
||||
decltype(std::begin(std::declval<range_type &>())),
|
||||
decltype(partial_range_detail::range_index_type<range_type>(nullptr))
|
||||
> (make_range)(range_type &t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
@ -431,18 +527,23 @@ inline partial_range_t<I> (make_range)(T &t)
|
|||
*/
|
||||
template <typename T,
|
||||
typename index_begin_type,
|
||||
typename index_end_type,
|
||||
typename I = decltype(begin(std::declval<T &&>()))
|
||||
typename index_end_type
|
||||
>
|
||||
partial_range_t<I> (partial_const_range)(const char *file, const unsigned line, const char *estr, const T &&t, const index_begin_type &index_begin, const index_end_type &index_end) = delete;
|
||||
partial_range_t<decltype(begin(std::declval<T &&>()))> (partial_const_range)(const char *file, const unsigned line, const char *estr, const T &&t, const index_begin_type &index_begin, const index_end_type &index_end) = delete;
|
||||
|
||||
template <typename T,
|
||||
typename index_end_type,
|
||||
typename I = decltype(begin(std::declval<T &&>()))
|
||||
typename index_end_type
|
||||
>
|
||||
partial_range_t<I> (partial_const_range)(const char *file, const unsigned line, const char *estr, const T &&t, const index_end_type &index_end) = delete;
|
||||
partial_range_t<decltype(begin(std::declval<T &&>()))> (partial_const_range)(const char *file, const unsigned line, const char *estr, const T &&t, const index_end_type &index_end) = delete;
|
||||
|
||||
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
||||
/* This macro is conditionally defined, because in the #else case, a
|
||||
* bare invocation supplies all required parameters. In the #else case:
|
||||
* - `required_buffer_size` is not a template parameter
|
||||
* - all other template parameters can be deduced
|
||||
* - the file, line, and string form of the expression are not function
|
||||
* parameters.
|
||||
*/
|
||||
#define unchecked_partial_range(T,...) unchecked_partial_range<partial_range_detail::required_buffer_size<sizeof(__FILE__), sizeof(#T)>>(__FILE__, __LINE__, #T, T, ##__VA_ARGS__)
|
||||
#endif
|
||||
#define partial_range(T,...) partial_range<partial_range_detail::required_buffer_size<sizeof(__FILE__), sizeof(#T)>>(__FILE__, __LINE__, #T, T, ##__VA_ARGS__)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "partial_range.h"
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#define BOOST_TEST_DYN_LINK
|
||||
|
@ -8,6 +9,8 @@
|
|||
#define DXX_TEST_IGNORE_RETURN(EXPR) ({ auto &&r = EXPR; static_cast<void>(r); })
|
||||
#define OPTIMIZER_HIDE_VARIABLE(V) asm("" : "=rm" (V) : "0" (V) : "memory")
|
||||
|
||||
BOOST_TEST_SPECIALIZED_COLLECTION_COMPARE(std::vector<int>);
|
||||
|
||||
BOOST_AUTO_TEST_CASE(exception_past_end)
|
||||
{
|
||||
std::vector<int> vec;
|
||||
|
@ -97,3 +100,35 @@ BOOST_AUTO_TEST_CASE(range_slice_reversed_begin_1_end_3)
|
|||
std::vector<int> expected{3, 2};
|
||||
BOOST_TEST(out == expected);
|
||||
}
|
||||
|
||||
/* Type system tests can be done at compile-time. Applying them as
|
||||
* static_assert can produce a better error message than letting it fail
|
||||
* at runtime.
|
||||
*/
|
||||
template <typename Expected, typename partial_range_type, typename index_type = typename partial_range_type::index_type>
|
||||
struct assert_index_type : std::true_type
|
||||
{
|
||||
static_assert(std::is_same<Expected, index_type>::value);
|
||||
};
|
||||
|
||||
static_assert(assert_index_type<void, decltype(partial_range(std::declval<std::vector<int>&>(), 0u, 1u))>::value);
|
||||
template <typename T>
|
||||
struct custom_index_type_only : std::array<int, 1>
|
||||
{
|
||||
using index_type = T;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct custom_index_type : std::array<int, 1>
|
||||
{
|
||||
using index_type = T;
|
||||
void operator[](typename std::remove_reference<T>::type);
|
||||
};
|
||||
enum class e1 : unsigned char;
|
||||
|
||||
/* The type is `void` because resolving index_type fails since `int *`
|
||||
* is not a valid argument type to operator[].
|
||||
*/
|
||||
static_assert(assert_index_type<void, decltype(partial_range(std::declval<custom_index_type_only<int *>&>(), 0u, 1u))>::value);
|
||||
static_assert(assert_index_type<std::size_t, decltype(partial_range(std::declval<custom_index_type<std::size_t>&>(), 0u, 1u))>::value);
|
||||
static_assert(assert_index_type<e1, decltype(partial_range(std::declval<custom_index_type<e1>&>(), 0u, 1u))>::value);
|
||||
|
|
Loading…
Reference in a new issue