diff --git a/include/adm/detail/get.hpp b/include/adm/detail/get.hpp new file mode 100644 index 00000000..d42d21ff --- /dev/null +++ b/include/adm/detail/get.hpp @@ -0,0 +1,41 @@ +// +// Created by Richard Bailey on 01/07/2022. +// + +#ifndef LIBADM_GET_HPP +#define LIBADM_GET_HPP +#include +#include + +namespace adm { namespace detail { +// Returns an optional child of a parent element +template +std::optional get_subelement_if(ParentT const& parent) { + if(parent.template has()) { + return parent.template get(); + } + return {}; +} + +// Extracts types from Ts..., calls get_if with each T, returns tuple of all results +template +auto get_subelements_indexed(std::index_sequence, ParentT const& doc) { + return std::make_tuple(get_subelement_if>>(doc)...); +} + +template +auto get_element_range(ParentT const& parent) { + return parent.template getElements(); +} + +template +auto get_element_range_indexed(std::index_sequence, ParentT const& doc) { + return std::make_tuple(get_element_range>>(doc)...); +} + +} + +template +struct ElementList{}; +} // namespace adm::detail +#endif //LIBADM_GET_HPP diff --git a/include/adm/helper/visit.hpp b/include/adm/helper/visit.hpp new file mode 100644 index 00000000..473a11eb --- /dev/null +++ b/include/adm/helper/visit.hpp @@ -0,0 +1,88 @@ +#pragma once +#include +#include +#include "adm/detail/get.hpp" + +namespace adm { + // Get a tuple of optional child elements from a parent + // Optional will be empty when ParentT::has() returns false. + template + auto get_subelements(ParentT const& parent) { + return detail::get_subelements_indexed( + std::make_index_sequence(), + parent); + } + + // Get a tuple of ElementRange child elements from a parent + template + auto get_elements(ParentT const& parent) { + return detail::get_element_range_indexed( + std::make_index_sequence(), + parent); + } + + // For using with type lists, need to wrap in a struct as can't pass + // function templates into templates + template + struct GetSub; + + // Specialisation to grab the type list from the tuple and apply it to get_elements + template + struct GetSub> { + template + auto operator()(T const& parent) { + return get_subelements(parent); + } + }; + template + struct GetElementRangesS; + + template + struct GetElementRangesS> { + auto operator()(Document const& parent) const { + return get_elements(parent); + } + }; + + // A bit of a hack to make the callable wrapper look like a normal function + template + static constexpr GetElementRangesS getElementRanges = GetElementRangesS{}; + + // This does the actual visitation + template + void do_visit(VisitorT&& v, Args&&... args) { + (..., v(std::forward(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 + struct RangeVisitor : public VisitorT { + template + void operator()(RangeT range) { + for (auto const& e : range) { + if(e) { + auto& v = static_cast(*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 + void visit(std::tuple&& 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(std::forward(visitor))](auto&&... args) mutable { + do_visit(std::forward(std::get<0>(v)), + std::forward(args)...); + }, + std::forward>(t)); + } +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6109fb1d..7ee03afb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ad63e7f..bcb8d07b 100755 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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") diff --git a/tests/visit_tests.cpp b/tests/visit_tests.cpp new file mode 100644 index 00000000..5a472a78 --- /dev/null +++ b/tests/visit_tests.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +// +// 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; + + RangeVisitor counter; + // That this works proves visitor can be passed as lvalue ref + // If it couldn't the counts would all be 0 + visit(getElementRanges(*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(*doc), RangeVisitor{}); +} \ No newline at end of file