Compiler error messages are generally better when reporting a misuse that fails a requires() versus reporting a misuse that fails a std::enable_if. In some cases, this also makes the code clearer, and avoids the need for dummy template parameters as a place to invoke std::enable_if.
592 lines
22 KiB
C++
592 lines
22 KiB
C++
/*
|
|
* This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
|
|
* It is copyright by its individual contributors, as recorded in the
|
|
* project's Git history. See COPYING.txt at the top level for license
|
|
* terms and a link to the Git history.
|
|
*/
|
|
#pragma once
|
|
#include <array>
|
|
#include <stdexcept>
|
|
#include <iterator>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include "fwd-partial_range.h"
|
|
#include <memory>
|
|
#include "dxxsconf.h"
|
|
|
|
/* If no value was specified for DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE,
|
|
* then define it to true for NDEBUG builds and false for debug builds.
|
|
*
|
|
* When DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE is true, the partial_range
|
|
* exception is a global scope structure.
|
|
*
|
|
* When DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE is false, the
|
|
* partial_range exception is nested in partial_range_t<I>, so that the
|
|
* exception type encodes the range type.
|
|
*
|
|
* Encoding the range type in the exception produces a more descriptive
|
|
* message when the program aborts for an uncaught exception, but
|
|
* produces larger debug information and many nearly-redundant
|
|
* instantiations of the report function. Using the global scope
|
|
* structure avoids those redundant instances, but makes the exception
|
|
* description less descriptive.
|
|
*
|
|
* Choose false if you expect to debug partial_range exceptions or
|
|
* report them to someone who will debug them for you. Choose true if
|
|
* you want a smaller program.
|
|
*/
|
|
#ifndef DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
|
|
#ifdef NDEBUG
|
|
#define DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE 1
|
|
#else
|
|
#define DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE 0
|
|
#endif
|
|
#endif
|
|
|
|
namespace partial_range_detail
|
|
{
|
|
|
|
#define REPORT_FORMAT_STRING "%s:%u: %s %lu past %p end %lu \"%s\""
|
|
/* 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)
|
|
{
|
|
std::snprintf(buf.data(), buf.size(), REPORT_FORMAT_STRING, file, line, desc, expr, reinterpret_cast<const void *>(t), d, estr);
|
|
}
|
|
#undef REPORT_FORMAT_STRING
|
|
|
|
/*
|
|
* These are placed out of line from the class and used as an
|
|
* indirection, so that the compiler can pick std::begin/std::end or ADL
|
|
* appropriate alternatives, if they exist.
|
|
*/
|
|
template <typename T>
|
|
inline auto adl_begin(T &t)
|
|
{
|
|
using std::begin;
|
|
return begin(t);
|
|
}
|
|
|
|
template <typename T>
|
|
inline auto adl_end(T &t)
|
|
{
|
|
using std::end;
|
|
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 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;
|
|
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
|
|
* code can unconditionally use the qualified type name. Using a
|
|
* typedef here instead of a preprocessor macro at the usage sites
|
|
* causes the debug information to be very slightly bigger, but has
|
|
* no effect on the size of the generated code and is easier to
|
|
* maintain.
|
|
*/
|
|
#if DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
|
|
using partial_range_error =
|
|
#endif
|
|
struct partial_range_error;
|
|
iterator m_begin, m_end;
|
|
partial_range_t(iterator b, iterator e) :
|
|
m_begin(b), m_end(e)
|
|
{
|
|
}
|
|
partial_range_t(const partial_range_t &) = default;
|
|
partial_range_t(partial_range_t &&) = default;
|
|
partial_range_t &operator=(const partial_range_t &) = default;
|
|
template <typename T>
|
|
partial_range_t(T &&t) :
|
|
m_begin(partial_range_detail::adl_begin(t)), m_end(partial_range_detail::adl_end(t))
|
|
{
|
|
/* If `T &&`, after reference collapsing, is an lvalue
|
|
* reference, then the object referenced by `t` will remain in
|
|
* scope after the statement that called this constructor, and
|
|
* there is no need to check whether the object `t` owns the
|
|
* iterated range.
|
|
*
|
|
* Otherwise, if `t` is an rvalue reference, assert that `t` is
|
|
* a view onto a range, rather than owning the range. A `t`
|
|
* that owns the storage would leave the iterators dangling.
|
|
*
|
|
* These checks are not precise. It is possible to have a type
|
|
* T that remains in scope, but frees its storage early, and
|
|
* leaves the range dangling. It is possible to have a type T
|
|
* that owns the storage, and is not destroyed after the
|
|
* containing statement terminates. Neither are good designs,
|
|
* and neither can be handled here. These checks attempt to
|
|
* catch obvious mistakes.
|
|
*/
|
|
if constexpr (!std::is_lvalue_reference<T &&>::value)
|
|
static_assert(!T::range_owns_iterated_storage::value, "rvalue reference to range requires that the range is a view, not an owner");
|
|
}
|
|
[[nodiscard]]
|
|
iterator begin() const { return m_begin; }
|
|
[[nodiscard]]
|
|
iterator end() const { return m_end; }
|
|
[[nodiscard]]
|
|
bool empty() const
|
|
{
|
|
return m_begin == m_end;
|
|
}
|
|
[[nodiscard]]
|
|
std::size_t size() const { return std::distance(m_begin, m_end); }
|
|
[[nodiscard]]
|
|
std::reverse_iterator<iterator> rbegin() const
|
|
{
|
|
return std::reverse_iterator<iterator>{m_end};
|
|
}
|
|
[[nodiscard]]
|
|
std::reverse_iterator<iterator> rend() const
|
|
{
|
|
return std::reverse_iterator<iterator>{m_begin};
|
|
}
|
|
[[nodiscard]]
|
|
partial_range_t<std::reverse_iterator<iterator>, index_type> reversed() const
|
|
{
|
|
return {rbegin(), rend()};
|
|
}
|
|
};
|
|
|
|
#if DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
|
|
struct partial_range_error
|
|
#else
|
|
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
|
|
{
|
|
using std::out_of_range::out_of_range;
|
|
template <std::size_t N>
|
|
[[noreturn]]
|
|
__attribute_cold
|
|
static void report(const char *file, unsigned line, const char *estr, const char *desc, unsigned long expr, const uintptr_t t, unsigned long d)
|
|
{
|
|
std::array<char, N> buf;
|
|
partial_range_detail::prepare_error_string(buf, d, estr, file, line, desc, expr, t);
|
|
throw partial_range_error(buf.data());
|
|
}
|
|
};
|
|
|
|
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
|
|
/*
|
|
* If EXPR and d are compile-time constant, and the (EXPR > d)
|
|
* branch is optimized out, then the expansion of
|
|
* PARTIAL_RANGE_COMPILE_CHECK_BOUND is optimized out, preventing
|
|
* the compile error.
|
|
*
|
|
* If EXPR and d are compile-time constant, and the (EXPR > d)
|
|
* branch is not optimized out, then this function is guaranteed to
|
|
* throw if it is ever called. In that case, the compile fails,
|
|
* since the program is guaranteed not to work as the programmer
|
|
* intends.
|
|
*
|
|
* If they are not compile-time constant, but the compiler can
|
|
* optimize based on constants, then it will optimize out the
|
|
* expansion of PARTIAL_RANGE_COMPILE_CHECK_BOUND, preventing the
|
|
* compile error. The function might throw on invalid inputs,
|
|
* including constant inputs that the compiler failed to recognize
|
|
* as compile-time constant.
|
|
*
|
|
* If the compiler cannot optimize based on the result of
|
|
* __builtin_constant_p (such as at -O0), then configure tests do
|
|
* not define DXX_CONSTANT_TRUE and the macro expands to nothing.
|
|
*/
|
|
#define PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S) \
|
|
(DXX_CONSTANT_TRUE(EXPR > d) && (DXX_ALWAYS_ERROR_FUNCTION(partial_range_will_always_throw_##S, #S " will always throw"), 0))
|
|
#else
|
|
#define PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S) static_cast<void>(0)
|
|
#endif
|
|
#define PARTIAL_RANGE_CHECK_BOUND(EXPR,S) \
|
|
PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S), \
|
|
((EXPR > d) && (range_exception::template report<required_buffer_size>(file, line, estr, #S, EXPR, t, d), 0))
|
|
PARTIAL_RANGE_CHECK_BOUND(index_begin, begin);
|
|
PARTIAL_RANGE_CHECK_BOUND(index_end, end);
|
|
#undef PARTIAL_RANGE_CHECK_BOUND
|
|
#undef PARTIAL_RANGE_COMPILE_CHECK_BOUND
|
|
}
|
|
|
|
#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);
|
|
/* Get the size of the object, according to the compiler's data
|
|
* analysis. If a size is known, check that the index values are in
|
|
* range. If no size is known, do nothing.
|
|
*
|
|
* The compiler may return a value of "unknown" here if the target
|
|
* object is dynamically allocated with unknown extents, or if the
|
|
* optimizer is not able to identify a specific object behind the
|
|
* pointer. The latter could happen if the object has a fixed size
|
|
* assigned in a different translation unit, and only the pointer to
|
|
* the object is visible in the translation unit evaluating this
|
|
* function.
|
|
*/
|
|
const std::size_t bos = __builtin_object_size(ptr, 1);
|
|
if (bos != static_cast<std::size_t>(-1))
|
|
check_range_bounds<range_exception, required_buffer_size>(file, line, estr, reinterpret_cast<uintptr_t>(ptr), index_begin, index_end, bos / sizeof(P));
|
|
}
|
|
|
|
/* When P refers to a temporary, this overload is picked. Temporaries
|
|
* have no useful address, so they cannot be checked. A temporary would
|
|
* be present if iterator.operator*() returns a proxy object, rather
|
|
* than a reference to an element in the container.
|
|
*/
|
|
template <typename range_exception, std::size_t required_buffer_size, typename P>
|
|
void check_range_object_size(const char *, unsigned, const char *, const P &&, std::size_t, std::size_t) {}
|
|
#endif
|
|
|
|
template <typename range_type, typename index_type>
|
|
std::true_type check_range_index_type_vs_provided_index_type(...);
|
|
|
|
template <
|
|
typename range_type,
|
|
typename provided_index_type,
|
|
/* If `range_type::index_type` is not defined, fail.
|
|
* If `range_type::index_type` is void, fail.
|
|
*/
|
|
typename range_index_type = typename std::remove_reference<typename std::remove_reference<range_type>::type::index_type &>::type
|
|
>
|
|
std::is_same<provided_index_type, range_index_type> check_range_index_type_vs_provided_index_type(std::nullptr_t);
|
|
|
|
template <typename range_type, typename index_type>
|
|
requires(std::is_enum<index_type>::value)
|
|
std::size_t cast_index_to_size(const index_type i)
|
|
{
|
|
static_assert(std::is_unsigned<typename std::underlying_type<index_type>::type>::value, "underlying_type of index_type must be unsigned");
|
|
/* If an enumerated index type is used, require that it be the type that
|
|
* the underlying range uses.
|
|
*/
|
|
static_assert(decltype(check_range_index_type_vs_provided_index_type<range_type, index_type>(nullptr))::value);
|
|
return static_cast<std::size_t>(i);
|
|
}
|
|
|
|
template <typename range_type, typename index_type>
|
|
requires(std::is_integral<index_type>::value)
|
|
std::size_t cast_index_to_size(const index_type i)
|
|
{
|
|
static_assert(std::is_unsigned<index_type>::value, "integral index_type must be unsigned");
|
|
/* Omit enforcement of range_type::index_type vs index_type, since many
|
|
* call sites provide numeric length limits.
|
|
static_assert(decltype(check_range_index_type_vs_provided_index_type<range_type, index_type>(nullptr))::value);
|
|
*/
|
|
/* Use an implicit conversion, so that an index_type of std::size_t does
|
|
* not cause a useless cast.
|
|
*/
|
|
return i;
|
|
}
|
|
|
|
}
|
|
|
|
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, index_type> unchecked_partial_range_advance(
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
const char *const file, const unsigned line, const char *const estr,
|
|
#endif
|
|
iterator_type range_begin, const std::size_t index_begin, const std::size_t index_end)
|
|
{
|
|
#ifdef DXX_CONSTANT_TRUE
|
|
/* Compile-time only check. Runtime handles (index_begin >
|
|
* index_end) correctly, and it can happen in a correct program. If
|
|
* it is guaranteed to happen, then the range is always empty, which
|
|
* likely indicates a bug.
|
|
*/
|
|
if (DXX_CONSTANT_TRUE(!(index_begin < index_end)))
|
|
DXX_ALWAYS_ERROR_FUNCTION(partial_range_is_always_empty, "offset never less than length");
|
|
#endif
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
/* Avoid iterator dereference if range is empty */
|
|
if (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;
|
|
/* Use <= so that (index_begin == 0) makes the expression
|
|
* always-true, so the compiler will optimize out the test.
|
|
*/
|
|
if (index_begin <= index_end)
|
|
{
|
|
using std::advance;
|
|
advance(range_begin, index_begin);
|
|
advance(range_end, index_end);
|
|
}
|
|
return {range_begin, range_end};
|
|
}
|
|
|
|
template <
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
std::size_t required_buffer_size,
|
|
#endif
|
|
typename range_type,
|
|
typename index_begin_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
|
|
range_type &range, const index_begin_type &index_begin, const index_end_type &index_end)
|
|
{
|
|
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
|
|
decltype(partial_range_detail::range_index_type<range_type>(nullptr)), iterator_type>(
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
file, line, estr,
|
|
#endif
|
|
std::begin(range),
|
|
partial_range_detail::cast_index_to_size<range_type, index_begin_type>(index_begin),
|
|
partial_range_detail::cast_index_to_size<range_type, index_end_type>(index_end)
|
|
);
|
|
}
|
|
|
|
template <
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
std::size_t required_buffer_size,
|
|
#endif
|
|
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]]
|
|
inline auto (unchecked_partial_range)(
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
const char *const file, const unsigned line, const char *const estr,
|
|
#endif
|
|
iterable &&it, const index_end_type &index_end)
|
|
{
|
|
return unchecked_partial_range<
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
required_buffer_size
|
|
#endif
|
|
>(
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
file, line, estr,
|
|
#endif
|
|
std::forward<iterable>(it), index_end_type{}, index_end
|
|
);
|
|
}
|
|
|
|
template <
|
|
std::size_t required_buffer_size,
|
|
typename range_type,
|
|
typename index_begin_type,
|
|
typename index_end_type,
|
|
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, range_type &range, const index_begin_type &index_begin, const index_end_type &index_end)
|
|
{
|
|
const auto sz_begin = partial_range_detail::cast_index_to_size<range_type, index_begin_type>(index_begin);
|
|
const auto sz_end = partial_range_detail::cast_index_to_size<range_type, index_end_type>(index_end);
|
|
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)), sz_begin, sz_end, std::size(range));
|
|
return unchecked_partial_range<
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
required_buffer_size,
|
|
#endif
|
|
range_type>(
|
|
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
|
|
file, line, estr,
|
|
#endif
|
|
range, sz_begin, sz_end
|
|
);
|
|
}
|
|
|
|
template <
|
|
std::size_t required_buffer_size,
|
|
typename range_type,
|
|
typename index_end_type
|
|
>
|
|
[[nodiscard]]
|
|
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, 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 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 range_type &range, const index_begin_type &index_begin, const index_end_type &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 range_type,
|
|
typename index_end_type
|
|
>
|
|
[[nodiscard]]
|
|
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 range_type, index_end_type>(file, line, estr, range, index_end);
|
|
}
|
|
|
|
template <typename range_type>
|
|
[[nodiscard]]
|
|
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;
|
|
}
|
|
|
|
/* Explicitly block use on rvalue t because returned partial_range_t<I>
|
|
* will outlive the rvalue.
|
|
*/
|
|
template <typename T,
|
|
typename index_begin_type,
|
|
typename index_end_type
|
|
>
|
|
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
|
|
>
|
|
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__)
|
|
#define partial_const_range(T,...) partial_const_range<partial_range_detail::required_buffer_size<sizeof(__FILE__), sizeof(#T)>>(__FILE__, __LINE__, #T, T, ##__VA_ARGS__)
|