Skip to content

Commit

Permalink
feat: Add TransformRange helper (#3060)
Browse files Browse the repository at this point in the history
This implements a transforming range adapter, similar to what will be available with the C++20+ ranges library, but much less powerful.

I'm planning to use this to gate mutable access to inner structures like child volumes in `TrackingVolume`, by using the `ConstDereference` policy for this range helper, which will always dereference and convert to const.
  • Loading branch information
paulgessinger committed Mar 27, 2024
1 parent aefffb3 commit 2b51bcb
Show file tree
Hide file tree
Showing 3 changed files with 460 additions and 1 deletion.
228 changes: 228 additions & 0 deletions Core/include/Acts/Utilities/TransformRange.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// This file is part of the Acts project.
//
// Copyright (C) 2024 CERN for the benefit of the Acts project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#pragma once

#include <iterator>
#include <type_traits>
#include <utility>

namespace Acts {

namespace detail {

template <typename Callable, typename iterator_t, bool force_const>
struct TransformRangeIterator;

/// This type implements a transforming range over a container.
/// It functions like a view, where all element access is passed through
/// a user-defined callable, that can process values, like dereferencing them
/// or calling a specific method.
///
/// @note The range and associated iterator maintain const-ness of the input,
/// i.e. if a const-qualified container is passed, the range and iterators
/// will not return any mutable references, even if they are mutable
/// themselves
/// @note The range and iterator assume the the callables return references
///
/// @tparam Callable The callable to apply to each element.
/// @tparam container_t The container to wrap.
template <typename Callable, typename container_t>
struct TransformRange {
private:
using internal_value_type = typename container_t::value_type;

static_assert(std::is_reference_v<decltype(Callable::apply(
std::declval<internal_value_type>()))>,
"The callable must return a reference type");

using raw_value_type = std::remove_reference_t<decltype(Callable::apply(
std::declval<internal_value_type>()))>;

public:
/// The underlying value type that is returned by the range.
/// If the input container has const-qualification, the range does
/// not expose mutable values
using value_type =
std::conditional_t<std::is_const_v<container_t>,
std::add_const_t<raw_value_type>, raw_value_type>;

using reference = value_type&;
using const_reference = const value_type&;

using iterator = TransformRangeIterator<
Callable,
std::conditional_t<std::is_const_v<container_t>,
typename container_t::const_iterator,
typename container_t::iterator>,
std::is_const_v<container_t>>;
using const_iterator =
TransformRangeIterator<Callable, typename container_t::const_iterator,
true>;

/// Construct a transforming range from a container. The first argument is
/// only used for type-deduction
/// @param container The container to wrap
explicit TransformRange(Callable&& /*callable*/, container_t& container)
: m_container(&container) {}

/// Access the i-th element of the underlying container, applying the
/// callable
/// @param i The index of the element to access
/// @return Reference to the transformed i-th element
reference operator[](std::size_t i) {
return Callable::apply((*m_container)[i]);
}

/// Access the i-th element of the underlying container, applying the
/// callable
/// @param i The index of the element to access
/// @return Const-reference to the transformed i-th element
const_reference operator[](std::size_t i) const {
return std::as_const(Callable::apply((*m_container)[i]));
}

/// Access the i-th element of the underlying container, applying the
/// callable
/// @param i The index of the element to access
/// @return Reference to the transformed i-th element
reference at(std::size_t i) { return Callable::apply(m_container->at(i)); }

/// Access the i-th element of the underlying container, applying the
/// callable
/// @param i The index of the element to access
/// @return Const-reference to the transformed i-th element
const_reference at(std::size_t i) const {
return std::as_const(Callable::apply(m_container->at(i)));
}

/// Return an iterator to the beginning of the underlying container
/// @return Iterator to the beginning of the range
iterator begin() { return iterator{m_container->begin()}; }

/// Return an iterator past the end of the underlying container
/// @return Iterator past the end of the range
iterator end() { return iterator{m_container->end()}; }

/// Return a const-iterator to the beginning of the underlying container
/// @return Const-iterator to the beginning of the range
const_iterator begin() const { return const_iterator{m_container->begin()}; }

/// Return a const-iterator past the end of the underlying container
/// @return Const-iterator past the end of the range
const_iterator cbegin() const { return begin(); }

/// Return a const-iterator to the beginning of the underlying container
/// @return Const-iterator to the beginning of the range
const_iterator end() const { return const_iterator{m_container->end()}; }

/// Return a const-iterator past the end of the underlying container
/// @return Const-iterator past the end of the range
const_iterator cend() const { return end(); }

/// Return the size of the underlying container
/// @return The size of the underlying container
std::size_t size() const { return m_container->size(); }

/// Check if the underlying container is empty
/// @return True if the underlying container is empty
bool empty() const { return m_container->empty(); }

private:
container_t* m_container;
};

/// This type is associated with @c TransformRange and implements the iterator
/// for the range. It applies the callable to the value that is dereferences.
/// It also maintains const-ness, if instructed, by returning const references
/// only.
/// @tparam Callable The callable to apply to the value
/// @tparam iterator_t The iterator type of the underlying container
template <typename Callable, typename iterator_t, bool force_const>
struct TransformRangeIterator {
private:
using internal_value_type =
typename std::iterator_traits<iterator_t>::value_type;

using raw_value_type = std::remove_reference_t<decltype(Callable::apply(
std::declval<internal_value_type>()))>;

public:
/// The underlying value type that is returned by the iterator.
/// If @c force_const is set to true, the iterator will only return
/// const-qualified references
using value_type =
std::conditional_t<force_const, std::add_const_t<raw_value_type>,
raw_value_type>;

using difference_type =
typename std::iterator_traits<iterator_t>::difference_type;
using pointer = std::remove_reference_t<value_type>*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;

/// Construct an iterator from an underlying iterator
explicit TransformRangeIterator(iterator_t iterator) : m_iterator(iterator) {}

/// Return a reference to the value that is transformed by the callable
/// @return Reference to the transformed value
reference operator*() { return Callable::apply(*m_iterator); }

/// Return a const-reference to the value that is transformed by the callable
/// @return Const-reference to the transformed value
reference operator*() const { return Callable::apply(*m_iterator); }

/// Advance the iterator
/// @return Reference to the iterator
TransformRangeIterator& operator++() {
++m_iterator;
return *this;
}

/// Compare two iterators for equality
/// @param other The other iterator to compare to
bool operator==(const TransformRangeIterator& other) const {
return m_iterator == other.m_iterator;
}

/// Compare two iterators for inequality
/// @param other The other iterator to compare to
bool operator!=(const TransformRangeIterator& other) const {
return m_iterator != other.m_iterator;
}

private:
iterator_t m_iterator;
};

/// Callable that dereferences a value
struct Dereference {
template <typename input_t>
constexpr static decltype(auto) apply(input_t&& value) {
return *value;
}
};

/// Callable that const-dereferences a value
struct ConstDereference {
template <typename input_t>
constexpr static decltype(auto) apply(input_t&& value) {
return std::as_const(*value);
}
};

/// Callable that calls the @c get method of a value
struct DotGet {
template <typename input_t>
constexpr static decltype(auto) apply(input_t&& value) {
return value.get();
}
};

} // namespace detail
} // namespace Acts
3 changes: 2 additions & 1 deletion Tests/UnitTests/Core/Utilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ add_unittest(TypeTraits TypeTraitsTest.cpp)
add_unittest(UnitVectors UnitVectorsTests.cpp)
add_unittest(Delegate DelegateTests.cpp)
add_unittest(HashedString HashedStringTests.cpp)
if (ACTS_BUILD_CUDA_FEATURES)
if (ACTS_BUILD_CUDA_FEATURES)
add_unittest(Cuda CudaTests.cu)
add_unittest(CudaMostSimplified CudaMostSimplifiedTests.cu)
endif()
Expand All @@ -50,3 +50,4 @@ target_compile_definitions(ActsUnitTestAnyDebug PRIVATE _ACTS_ANY_ENABLE_VERBOSE

add_unittest(ParticleData ParticleDataTests.cpp)
add_unittest(Zip ZipTests.cpp)
add_unittest(TransformRange TransformRangeTests.cpp)

0 comments on commit 2b51bcb

Please sign in to comment.