Skip to content
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
42 changes: 42 additions & 0 deletions include/wtf/buffer/buffer_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,13 @@ class BufferView {
template<concepts::WTFFloat Float2>
friend class BufferView;

template<typename TupleType, typename Visitor, typename... Args>
friend auto visit_contiguous_buffer_view(Visitor&& visitor, Args&&... args);

holder_type& holder_() { return *m_pholder_; }

const holder_type& holder_() const { return *m_pholder_; }

void valid_index_(size_type index) const {
if(index >= size()) {
throw std::out_of_range("BufferView: index out of range");
Expand Down Expand Up @@ -477,4 +484,39 @@ std::span<T> contiguous_buffer_cast(BufferView<FloatType>& buffer) {
return buffer.template value<T>();
}

/** @brief Wraps the process of calling a visitor with zero or more
* BufferView objects.
*
* @relates BufferView
*
* @tparam TupleType A std::tuple of floating-point types to try. Must be
* explicitly provided by the user.
* @tparam Visitor The type of the visitor to call. Must be a callable object
* capable of accepting `std::span<T>` objects for each
* possible T in @p TupleType. Will be inferred by the
* compiler.
* @tparam Args The types of the arguments to forward to the visitor. Each
* is expected to be downcastable to a ContiguousModel holding
* one of the types in @p TupleType. Will be inferred by the
* compiler.
*
* @param[in] visitor The visitor to call with the unwrapped std::span<T>
* objects.
* @param[in] args The ContiguousModel objects to unwrap and pass to the
* visitor.
*
* @return The return value of calling @p visitor with the unwrapped
* std::span<T> objects.
*
* @throw std::runtime_error if any of the @p args cannot be downcast to a
* ContiguousModel holding one of the types in
* @p TupleType. Strong throw guarantee.
* @throw ??? if calling @p visitor throws. Same throw guarantee.
*/
template<typename TupleType, typename Visitor, typename... Args>
auto visit_contiguous_buffer_view(Visitor&& visitor, Args&&... args) {
return detail_::visit_contiguous_view_model<TupleType>(
std::forward<Visitor>(visitor), args.holder_()...);
}

} // namespace wtf::buffer
11 changes: 11 additions & 0 deletions include/wtf/buffer/detail_/buffer_view_holder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ class BufferViewHolder {
*/
const_rtti_reference type() const { return m_rtti_; }

/** @brief Are the held elements read-only?
*
* @return true if the held elements are read-only, false otherwise.
*
* @throw None No-throw guarantee.
*/
bool is_const() const noexcept { return is_const_(); }

/** @brief Are the elements in the buffer contiguous?
*
* This method indicates whether the held buffer stores its elements
Expand Down Expand Up @@ -194,6 +202,9 @@ class BufferViewHolder {
/// The size of the held buffer
virtual size_type size_() const noexcept = 0;

/// Does the derived class hold read-only data
virtual bool is_const_() const noexcept = 0;

/// Does the derived class store the buffer contiguously in memory?
virtual bool is_contiguous_() const = 0;

Expand Down
69 changes: 67 additions & 2 deletions include/wtf/buffer/detail_/contiguous_view_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ class ContiguousViewModel
using const_model_type = ContiguousViewModel<const FloatType>;

/// Is *this aliasing a read-only buffer?
static constexpr bool is_const = std::is_const_v<FloatType>;
static constexpr bool m_is_const = std::is_const_v<FloatType>;

/// The type of buffer *this is acting like
using buffer_type =
std::conditional_t<is_const, const fp::Float, fp::Float>;
std::conditional_t<m_is_const, const fp::Float, fp::Float>;

public:
/// Type *this inherits from
Expand Down Expand Up @@ -153,6 +153,30 @@ class ContiguousViewModel
*/
const_pointer data() const { return m_buffer_.data(); }

/** @brief Returns the wrapped data as a std::span.
*
* Starting with C++20 this is the preferred way to access raw contiguous
* buffers.
*
* @return A std::span wrapping the underlying buffer.
*
* @throw None No-throw guarantee.
*/
auto span() { return std::span<FloatType>(data(), this->size()); }

/** @brief Returns the wrapped data as a std::span.
*
* This method is the same as the non-const version except that it ensures
* the resulting span is read-only.
*
* @return A std::span wrapping the underlying buffer.
*
* @throw None No-throw guarantee.
*/
auto span() const {
return std::span<const FloatType>(data(), this->size());
}

/** @brief Compares the elements in the buffer for exact equality.
*
* Value equal is defined as having the same elements in the same order.
Expand Down Expand Up @@ -195,6 +219,9 @@ class ContiguousViewModel
/// Calls span_type's size()
size_type size_() const noexcept override { return m_buffer_.size(); }

/// *this is read-only if FloatType is const
bool is_const_() const noexcept override { return m_is_const; }

/// *this always store data contiguously
bool is_contiguous_() const override { return true; }

Expand All @@ -210,4 +237,42 @@ class ContiguousViewModel
span_type m_buffer_;
};

/** @brief Wraps the process of calling a visitor with zero or more
* ContiguousViewModel objects.
*
* @relates ContiguousViewModel
*
* @tparam TupleType A std::tuple of floating-point types to try. Must be
* explicitly provided by the user.
* @tparam Visitor The type of the visitor to call. Must be a callable object
* capable of accepting `std::span<T>` objects for each
* possible T in @p TupleType. Will be inferred by the
* compiler.
* @tparam Args The types of the arguments to forward to the visitor. Each
* is expected to be downcastable to a ContiguousModel holding
* one of the types in @p TupleType. Will be inferred by the
* compiler.
*
* @param[in] visitor The visitor to call with the unwrapped std::span<T>
* objects.
* @param[in] args The ContiguousModel objects to unwrap and pass to the
* visitor.
*
* @return The return value of calling @p visitor with the unwrapped
* std::span<T> objects.
*
* @throw std::runtime_error if any of the @p args cannot be downcast to a
* ContiguousModel holding one of the types in
* @p TupleType. Strong throw guarantee.
* @throw ??? if calling @p visitor throws. Same throw guarantee.
*/
template<typename TupleType, typename Visitor, typename... Args>
auto visit_contiguous_view_model(Visitor&& visitor, Args&&... args) {
auto lambda = [&](auto&&... inner_args) {
return visitor(inner_args.span()...);
};
return wtf::detail_::dispatch<ContiguousViewModel, TupleType>(
lambda, std::forward<Args>(args)...);
}

} // namespace wtf::buffer::detail_
135 changes: 135 additions & 0 deletions tests/unit_tests/wtf/buffer/buffer_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,138 @@ TEMPLATE_LIST_TEST_CASE("contiguous_buffer_cast(BufferView)", "[wtf]",
REQUIRE(const_span[2] == three);
REQUIRE(const_span.data() == cdata);
}

struct ConstVisitorConstSpan {
ConstVisitorConstSpan(float* pdataf, double* pdatad) :
pdataf_corr(pdataf), pdatad_corr(pdatad) {}

auto operator()(std::span<const float> span) const {
REQUIRE(span.data() == pdataf_corr);
REQUIRE(span.size() == 3);
}

auto operator()(std::span<const double> span) const {
REQUIRE(span.data() == pdatad_corr);
REQUIRE(span.size() == 3);
}

auto operator()(std::span<float> lhs, std::span<double> rhs) const {
REQUIRE(lhs.data() == pdataf_corr);
REQUIRE(lhs.size() == 3);
REQUIRE(rhs.data() == pdatad_corr);
REQUIRE(rhs.size() == 3);
}

template<typename T, typename U>
auto operator()(std::span<T> lhs, std::span<U> rhs) const {
throw std::runtime_error("Only float, double supported");
}

float* pdataf_corr;
double* pdatad_corr;
};

struct VisitorConstSpan {
VisitorConstSpan(float* pdataf, double* pdatad) :
pdataf_corr(pdataf), pdatad_corr(pdatad) {}

template<typename T>
auto operator()(std::span<T> span) {
if constexpr(std::is_same_v<std::remove_const_t<T>, float>) {
REQUIRE(span.data() == pdataf_corr);
m_called_float = true;
} else {
REQUIRE(span.data() == pdatad_corr);
m_called_double = true;
}
}

template<typename T>
auto operator()(std::span<const T> span) {
if constexpr(std::is_same_v<std::remove_const_t<T>, float>) {
REQUIRE(span.data() == pdataf_corr);
m_called_cfloat = true;
} else {
REQUIRE(span.data() == pdatad_corr);
m_called_cdouble = true;
}
REQUIRE(span.size() == 3);
}

auto operator()(std::span<float> lhs, std::span<double> rhs) {
REQUIRE(lhs.data() == pdataf_corr);
REQUIRE(lhs.size() == 3);
REQUIRE(rhs.data() == pdatad_corr);
REQUIRE(rhs.size() == 3);
m_called_float_double = true;
}

template<typename T, typename U>
auto operator()(std::span<T> lhs, std::span<U> rhs) const {
throw std::runtime_error("Only float, double supported");
}

bool m_called_float = false;
bool m_called_cfloat = false;
bool m_called_double = false;
bool m_called_cdouble = false;
bool m_called_float_double = false;

float* pdataf_corr;
double* pdatad_corr;
};

TEST_CASE("visit_contiguous_buffer_view") {
std::vector<float> valf{1.0, 2.0, 3.0};
std::vector<double> vald{1.0, 2.0, 3.0};
auto pdataf = valf.data();
auto pdatad = vald.data();

ConstVisitorConstSpan visitor(pdataf, pdatad);
VisitorConstSpan visitor2(pdataf, pdatad);

BufferView<wtf::fp::Float> modelf(pdataf, valf.size());
BufferView<wtf::fp::Float> modeld(pdatad, vald.size());

using type_tuple = std::tuple<float, double>;

SECTION("one argument") {
SECTION("call span<const float>/ span<const double> overloads") {
visit_contiguous_buffer_view<type_tuple>(visitor, modelf);
visit_contiguous_buffer_view<type_tuple>(visitor,
std::as_const(modelf));
visit_contiguous_buffer_view<type_tuple>(visitor, modeld);
visit_contiguous_buffer_view<type_tuple>(visitor,
std::as_const(modeld));
}

SECTION("call span<const T> overload") {
const auto& cmodelf = modelf;
visit_contiguous_buffer_view<type_tuple>(visitor2, cmodelf);
REQUIRE(visitor2.m_called_cfloat);
REQUIRE_FALSE(visitor2.m_called_float);

const auto& cmodeld = modeld;
visit_contiguous_buffer_view<type_tuple>(visitor2, cmodeld);
REQUIRE(visitor2.m_called_cdouble);
REQUIRE_FALSE(visitor2.m_called_double);
}

SECTION("calls span<T> overload") {
visit_contiguous_buffer_view<type_tuple>(visitor2, modelf);
REQUIRE_FALSE(visitor2.m_called_cfloat);
REQUIRE(visitor2.m_called_float);

visit_contiguous_buffer_view<type_tuple>(visitor2, modeld);
REQUIRE_FALSE(visitor2.m_called_cdouble);
REQUIRE(visitor2.m_called_double);
}
}

SECTION("Two arguments") {
visit_contiguous_buffer_view<type_tuple>(visitor, modelf, modeld);

visit_contiguous_buffer_view<type_tuple>(visitor2, modelf, modeld);
REQUIRE(visitor2.m_called_float_double);
}
}
60 changes: 60 additions & 0 deletions tests/unit_tests/wtf/buffer/detail_/contiguous_view_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,63 @@ TEMPLATE_LIST_TEST_CASE("ContiguousViewModel", "[wtf]", all_fp_types) {
REQUIRE_FALSE(const_model.are_equal(other_const_model));
}
}

struct CheckVisitContiguousViewModel {
CheckVisitContiguousViewModel(float* pdataf, double* pdatad) :
pdataf_corr(pdataf), pdatad_corr(pdatad) {}

auto operator()(std::span<const float> span) const {
REQUIRE(span.data() == pdataf_corr);
REQUIRE(span.size() == 3);
}

auto operator()(std::span<const double> span) const {
REQUIRE(span.data() == pdatad_corr);
REQUIRE(span.size() == 3);
}

auto operator()(std::span<float> lhs, std::span<double> rhs) const {
REQUIRE(lhs.data() == pdataf_corr);
REQUIRE(lhs.size() == 3);
REQUIRE(rhs.data() == pdatad_corr);
REQUIRE(rhs.size() == 3);
}

template<typename T, typename U>
auto operator()(std::span<T> lhs, std::span<U> rhs) const {
throw std::runtime_error("Only float, double supported");
}

float* pdataf_corr;
double* pdatad_corr;
};

TEST_CASE("visit_contiguous_view_model") {
std::vector<float> valf{1.0, 2.0, 3.0};
std::vector<double> vald{1.0, 2.0, 3.0};
auto pdataf = valf.data();
auto pdatad = vald.data();

CheckVisitContiguousViewModel visitor(pdataf, pdatad);

ContiguousViewModel<float> modelf(pdataf, valf.size());
ContiguousViewModel<double> modeld(pdatad, vald.size());
ContiguousViewModel<const float> const_modelf(pdataf, valf.size());
ContiguousViewModel<const double> const_modeld(pdatad, vald.size());

using type_tuple = std::tuple<float, double>;

SECTION("one argument") {
visit_contiguous_view_model<type_tuple>(visitor, modelf);
visit_contiguous_view_model<type_tuple>(visitor, modeld);
visit_contiguous_view_model<type_tuple>(visitor, const_modelf);
visit_contiguous_view_model<type_tuple>(visitor, const_modeld);
}

SECTION("Two arguments") {
visit_contiguous_view_model<type_tuple>(visitor, modelf, modeld);
REQUIRE_THROWS_AS(visit_contiguous_view_model<type_tuple>(
visitor, const_modelf, const_modeld),
std::runtime_error);
}
}