diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index a392ff614693e0cba62a64757decfbb14506f895..4141942d6f07e3af5143be43a0addbae196823e6 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -184,7 +184,8 @@ set (COMMON_UNIT_TESTS_LIB_SRC_FILES utils/RegexTest.cpp utils/UtilsTest.cpp UserIdentityTest.cpp - optionalTest.cpp) + optionalTest.cpp + rangeTest.cpp) add_library (ctacommonunittests SHARED ${COMMON_UNIT_TESTS_LIB_SRC_FILES}) diff --git a/common/range.hpp b/common/range.hpp new file mode 100644 index 0000000000000000000000000000000000000000..96b3eccd7f4970038b021f1787a6e96a2ae804aa --- /dev/null +++ b/common/range.hpp @@ -0,0 +1,95 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <limits> +#include "common/exception/Exception.hpp" + +namespace cta { +template <typename T> +class range: public std::initializer_list<T> { +public: + range (T begin, T end, T step=1): m_begin(begin), m_end(end), m_step(step) {} + + class iterator { + private: + friend range; + enum class direction: bool { forward, backward }; + iterator(T begin, T end, T step, iterator::direction dir): m_val(begin), m_limit(end), m_step(step), m_dir(dir) {} + public: + CTA_GENERATE_EXCEPTION_CLASS(DereferencingPastEnd); + T operator*() { if (m_val == m_limit) throw DereferencingPastEnd("In range::operator*(): dereferencing out of bounds"); return m_val; } + iterator & operator++() { doInc(); return *this; } + iterator operator++(int) { iterator ret(*this); doInc(); return ret; } + void doInc() { + switch (m_dir) { + // Increment/decrement variable, preventing over/underflow, and overshooting the limit. + case direction::forward: + // Prevent overflow + if (m_step > std::numeric_limits<T>::max() - m_val) { + m_val=m_limit; + } else { + m_val+=m_step; + // Prevent overshoot. + if (m_val > m_limit) + m_val=m_limit; + } + break; + case direction::backward: + // Prevent underflow + if (m_step > m_val - std::numeric_limits<T>::max() ) { + m_val=m_limit; + } else { + m_val-=m_step; + if (m_val < m_limit) + m_val = m_limit; + } + break; + } + } + bool operator==(const iterator& other) { checkIteratorsComparable(other); return m_val == other.m_val; } + bool operator!=(const iterator& other) { checkIteratorsComparable(other); return m_val != other.m_val; } + CTA_GENERATE_EXCEPTION_CLASS(NonComparableIterators); + private: + void checkIteratorsComparable(const iterator & other) const { + if (m_limit != other.m_limit || m_step != other.m_step || m_dir != other.m_dir) + throw NonComparableIterators("In range::checkIteratorsComparable(): comparing iterators from different ranges."); + } + + T m_val; + const T m_limit, m_step; + direction m_dir; + }; + + iterator cbegin() const { + return iterator(m_begin, m_end, m_step, m_begin<=m_end?iterator::direction::forward:iterator::direction::backward); + } + + iterator cend() const { + return iterator(m_end, m_end, m_step, m_begin<=m_end?iterator::direction::forward:iterator::direction::backward); + } + + iterator begin() const { return cbegin(); } + iterator end() const { return cend(); } +private: + const T m_begin; + const T m_end; + const T m_step; +}; +} // namesapce cta diff --git a/common/rangeTest.cpp b/common/rangeTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b7c4413401139e2483d240f5da3de2783012bc70 --- /dev/null +++ b/common/rangeTest.cpp @@ -0,0 +1,53 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "range.hpp" +#include <list> + +#include <gtest/gtest.h> + +namespace unitTests { + +TEST(cta_rangeTest, basic) { + using namespace cta; + + std::list<size_t> l; + // Test forward + for (const auto &i: range<size_t>(0, 4)) l.emplace_back(i); + ASSERT_EQ(std::list<size_t>({0,1,2,3}), l); + l.clear(); + // Test forward with step. + for (const auto &i: range<size_t>(0, 10, 3)) l.emplace_back(i); + ASSERT_EQ(std::list<size_t>({0,3,6,9}), l); + l.clear(); + // Test forward overflow + const auto max = std::numeric_limits<size_t>::max(); + for (const auto &i: range<size_t>(max - 10, max, 3)) l.emplace_back(i); + ASSERT_EQ(std::list<size_t>({max-10, max-7, max-4, max-1}), l); + l.clear(); + // Test backwards + for (const auto &i: range<size_t>(10, 5)) l.emplace_back(i); + ASSERT_EQ(std::list<size_t>({10, 9, 8, 7, 6}), l); + l.clear(); + // Test backwards with step and underflow. + for (const auto &i: range<size_t>(10, 0, 3)) l.emplace_back(i); + ASSERT_EQ(std::list<size_t>({10, 7, 4, 1}), l); + +} + +} \ No newline at end of file