8000 Element visiting by rsjbailey · Pull Request #148 · ebu/libadm · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Element visiting #148

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
41 changes: 41 additions & 0 deletions include/adm/detail/get.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Created by Richard Bailey on 01/07/2022.
//

#ifndef LIBADM_GET_HPP
#define LIBADM_GET_HPP
#include <optional>
#include <tuple>

namespace adm { namespace detail {
// Returns an optional child of a parent element
template<typename ElementT, typename ParentT>
std::optional<ElementT> get_subelement_if(ParentT const& parent) {
if(parent.template has<ElementT>()) {
return parent.template get<ElementT>();
}
return {};
}

// Extracts types from Ts..., calls get_if<T> with each T, returns tuple of all results
template<typename... Ts, std::size_t... Indexes, typename ParentT>
auto get_subelements_indexed(std::index_sequence<Indexes...>, ParentT const& doc) {
return std::make_tuple(get_subelement_if<std::tuple_element_t<Indexes, std::tuple<Ts...>>>(doc)...);
}

template<typename ElementT, typename ParentT>
auto get_element_range(ParentT const& parent) {
return parent.template getElements<ElementT>();
}

template<typename... Ts, std::size_t... Indexes, typename ParentT>
auto get_element_range_indexed(std::index_sequence<Indexes...>, ParentT const& doc) {
return std::make_tuple(get_element_range<std::tuple_element_t<Indexes, std::tuple<Ts...>>>(doc)...);
}

}

template<typename... Ts>
struct ElementList{};
} // namespace adm::detail
#endif //LIBADM_GET_HPP
88 changes: 88 additions & 0 deletions include/adm/helper/visit.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#pragma once
#include <optional>
#include <iostream>
#include "adm/detail/get.hpp"

namespace adm {
// Get a tuple of optional<ElementT> child elements from a parent
// Optional will be empty when ParentT::has<ElementT>() returns false.
template<typename... ElementTs, typename ParentT>
auto get_subelements(ParentT const& parent) {
return detail::get_subelements_indexed<ElementTs...>(
std::make_index_sequence<sizeof...(ElementTs)>(),
parent);
}

// Get a tuple of ElementRange<ElementT> child elements from a parent
template<typename... ElementTs, typename ParentT>
auto get_elements(ParentT const& parent) {
return detail::get_element_range_indexed<ElementTs...>(
std::make_index_sequence<sizeof...(ElementTs)>(),
parent);
}

// For using with type lists, need to wrap in a struct as can't pass
// function templates into templates
template <typename T>
struct GetSub;

// Specialisation to grab the type list from the tuple and apply it to get_elements
template <typename... Ts>
struct GetSub<ElementList<Ts...>> {
template <typename T>
auto operator()(T const& parent) {
return get_subelements<Ts...>(parent);
}
};
template <typename T>
struct GetElementRangesS;

template <typename... Ts>
struct GetElementRangesS<ElementList<Ts...>> {
auto operator()(Document const& parent) const {
return get_elements<Ts...>(parent);
}
};

// A bit of a hack to make the callable wrapper look like a normal function
template<typename T>
static constexpr GetElementRangesS<T> getElementRanges = GetElementRangesS<T>{};

// This does the actual visitation
template <typename VisitorT, typename... Args>
void do_visit(VisitorT&& v, Args&&... args) {
(..., v(std::forward<Args>(args)));
}

// Wraps a visitor that handles element types.
// iterates though the range of each element type.
// calls the wrapped visitor after de-referencing each element if valid.
template<typename VisitorT>
struct RangeVisitor : public VisitorT {
template <typename RangeT>
void operator()(RangeT range) {
for (auto const& e : range) {
if(e) {
auto& v = static_cast<VisitorT&>(*this);
v(*e);
}
}
}
};

// the api function for users, takes a tuple of parameters and an object with call
// operators for all the parameter types (at least) then calls the object
// with each element of the tuple
template <typename VisitorT, typename... Ts>
void visit(std::tuple<Ts...>&& t, VisitorT&& visitor) {
std::apply(
// The shenanigans here with the tuple is for correctly forward capturing the visitor
// doesn't work without a wrapper class and tuple works as the wrapper
// mutable needed to modify v within the lambda
[v = std::tuple<VisitorT>(std::forward<VisitorT>(visitor))](auto&&... args) mutable {
do_visit(std::forward<VisitorT>(std::get<0>(v)),
std::forward<decltype(args)>(args)...);
},
std::forward<std::tuple<Ts...>>(t));
}
}
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ if (${CMAKE_VERSION} VERSION_LESS "3.8.0")
# this for CMake < 3.8
target_compile_features(adm PUBLIC cxx_generic_lambdas)
else()
target_compile_features(adm PUBLIC cxx_std_14)
target_compile_features(adm PUBLIC cxx_std_17)
endif()
set_target_properties(adm PROPERTIES CXX_EXTENSIONS OFF)

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ add_adm_test("route_tracer_tests")
add_adm_test("screen_edge_lock_tests")
add_adm_test("speaker_position_tests")
add_adm_test("type_descriptor_tests")
add_adm_test("visit_tests")
add_adm_test("xml_audio_block_format_objects_tests")
add_adm_test("xml_loudness_metadata_tests")
add_adm_test("xml_parser_audio_block_format_direct_speakers_tests")
Expand Down
64 changes: 64 additions & 0 deletions tests/visit_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include <catch2/catch.hpp>
#include <adm/document.hpp>
#include <adm/helper/visit.hpp>
#include <adm/utilities/object_creation.hpp>
//
// Created by Richard Bailey on 01/07/2022.
//

using namespace adm;

struct ElementCounts {
std::size_t programmes{0};
std::size_t contents{0};
std::size_t objects{0};
std::size_t tracks{0};
std::size_t packs{0};
std::size_t channels{0};
std::size_t streams{0};
std::size_t trackUids{0};
};

struct ElementCounter {
// The ctors are only declared for the purposes of testing, a plain struct
// would work just fine.
ElementCounter() = default;
ElementCounter(ElementCounter const& other) = delete;
ElementCounter(ElementCounter && other) noexcept = default;
void operator()(AudioProgramme const& programme) { ++counts.programmes; }
void operator()(AudioContent const& content) { ++counts.contents; }
void operator()(AudioObject const& object) { ++counts.objects; }
void operator()(AudioTrackFormat const& track) { ++counts.tracks; }
void operator()(AudioPackFormat const& pack) { ++counts.packs; }
void operator()(AudioChannelFormat const& pack) { ++counts.channels; }
void operator()(AudioStreamFormat const& stream) { ++counts.streams; }
void operator()(AudioTrackUid const& trackUid) { ++counts.trackUids; }
ElementCounts counts;
};

TEST_CASE("Visit document") {
auto doc = Document::create();
addSimpleObjectTo(doc, "Test");

using DocElements = ElementList<AudioProgramme, AudioContent, AudioObject,
AudioPackFormat, AudioChannelFormat, AudioTrackFormat,
AudioStreamFormat, AudioTrackUid>;

RangeVisitor<ElementCounter> counter;
// That this works proves visitor can be passed as lvalue ref
// If it couldn't the counts would all be 0
visit(getElementRanges<DocElements>(*doc), counter);
auto counts = counter.counts;
REQUIRE(counts.programmes == 0);
REQUIRE(counts.contents == 0);
REQUIRE(counts.objects == 1);
REQUIRE(counts.tracks == 1);
REQUIRE(counts.packs == 1);
REQUIRE(counts.channels == 1);
REQUIRE(counts.streams == 1);
REQUIRE(counts.trackUids == 1);

// lvalue ref can't bind to temporary, copy ctor is deleted -
// That this compiles proves visitor can be passed as rvalue ref
visit(getElementRanges<DocElements>(*doc), RangeVisitor<ElementCounter>{});
}
0