diff --git a/.clang-tidy b/.clang-tidy index be507db..5b01c9d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -11,7 +11,8 @@ Checks: > -altera-unroll-loops, -llvmlibc-inline-function-decl, -cert-dcl21-cpp, - -fuchsia-default-arguments-declarations + -fuchsia-default-arguments-declarations, + -cppcoreguidelines-pro-type-union-access WarningsAsErrors: "*" @@ -28,8 +29,7 @@ CheckOptions: - { key: readability-identifier-naming.ClassMemberSuffix, value: _ } - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } - { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ } - - { key: readability-identifier-naming.EnumConstantCase, value: CamelCase } - - { key: readability-identifier-naming.EnumConstantPrefix, value: k } + - { key: readability-identifier-naming.EnumConstantCase, value: lower_case } - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } diff --git a/.gitignore b/.gitignore index d5180b2..d3f8fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ install docs .cache +*.profraw diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index ae49078..3eecdf9 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -5,9 +5,11 @@ #include "blocking_iterator.hpp" #include "nodiscard.hpp" +#include "result.hpp" #include "storage.hpp" #include +#include #include #include #include @@ -17,19 +19,6 @@ namespace msd { -/** - * @brief Exception thrown if trying to write on closed channel. - */ -class closed_channel : public std::runtime_error { - public: - /** - * @brief Constructs the exception with an error message. - * - * @param msg A descriptive message explaining the cause of the error. - */ - explicit closed_channel(const char* msg) : std::runtime_error{msg} {} -}; - /** * @brief Default storage for msd::channel. * @@ -74,6 +63,34 @@ struct is_static_storage : std::false_type {}; template struct is_static_storage : std::true_type {}; +/** + * @brief Exception thrown if trying to write on closed channel. + */ +class closed_channel : public std::runtime_error { + public: + /** + * @brief Constructs the exception with an error message. + * + * @param msg A descriptive message explaining the cause of the error. + */ + explicit closed_channel(const char* msg) : std::runtime_error{msg} {} +}; + +/** + * @brief Possible errors during a batch write operation. + */ +enum class batch_write_error : std::int8_t { + /** + * @brief The specified range exceeds the available capacity. + */ + range_exceeds_capacity, + + /** + * @brief The receiving channel is closed and cannot accept data. + */ + channel_is_closed, +}; + /** * @brief Thread-safe container for sharing data between threads. * @@ -180,6 +197,48 @@ class channel { return true; } + /** + * @brief Writes a range of elements into the channel in batch mode. + * + * This function attempts to write all elements from the input range [begin, end) + * into the channel. If the channel has a capacity and the range exceeds that capacity, + * the function returns an error. If the channel is closed, it also returns an error. + * + * @tparam InputIterator An input iterator type pointing to elements of type `T`. + * @param begin Iterator pointing to the beginning of the range to write. + * @param end Iterator pointing to the end of the range to write (exclusive). + * @return A result indicating success or containing a `batch_write_error`: + * - `batch_write_error::range_exceeds_capacity` if the range is too large to fit. + * - `batch_write_error::channel_is_closed` if the channel is already closed. + * - Empty (success) if all elements were successfully written. + * + * @note It takes the lock on the channel once for all elements and release it at the end. + */ + template + result batch_write(const InputIterator begin, const InputIterator end) + { + { + std::unique_lock lock{mtx_}; + wait_before_write(lock); + + if (is_closed_) { + return result{batch_write_error::channel_is_closed}; + } + + if (capacity_ > 0 && (static_cast(std::distance(begin, end)) + storage_.size()) > capacity_) { + return result{batch_write_error::range_exceeds_capacity}; + } + + for (InputIterator it = begin; it != end; ++it) { + storage_.push_back(*it); + } + } + + cnd_.notify_one(); + + return result{}; + } + /** * @brief Pops an element from the channel. * diff --git a/include/msd/result.hpp b/include/msd/result.hpp new file mode 100644 index 0000000..6767385 --- /dev/null +++ b/include/msd/result.hpp @@ -0,0 +1,111 @@ +// Copyright (C) 2020-2025 Andrei Avram + +#ifndef MSD_CHANNEL_RESULT_HPP_ +#define MSD_CHANNEL_RESULT_HPP_ + +/** @file */ + +namespace msd { + +/** + * @brief A result that contains either a value of type T or an error of type E. + * + * @tparam T The type of the value on success. + * @tparam E The type of the error on failure. + */ +template +class result { + public: + /** + * @brief Constructs an empty result (not valid). + */ + explicit result() = default; + + /** + * @brief Constructs a successful result with a value. + * + * @param value The value to store into the result. + */ + explicit result(T value) : value_{value} {} + + /** + * @brief Constructs an error result. + * + * @param error The error value to store. + */ + explicit result(E error) : has_error_{true}, error_{error} {} + + /** + * @brief Checks whether the result is a success. + * + * @return true if the result holds a value, false if it holds an error. + */ + explicit operator bool() const { return !has_error_; } + + /** + * @brief Gets the stored value. + * + * @return const T& Reference to the stored value. + * @warning Behavior is undefined if the result holds an error. + */ + const T& value() const { return value_; } + + /** + * @brief Gets the stored error. + * + * @return const E& Reference to the stored error. + * @warning Behavior is undefined if the result holds a value. + */ + const E& error() const { return error_; } + + private: + union { + T value_; + E error_; + }; + bool has_error_{}; +}; + +/** + * @brief Specialization of result for void success type. + * + * @tparam E The type of the error on failure. + */ +template +class result { + public: + /** + * @brief Constructs a successful void result. + */ + result() = default; + + /** + * @brief Constructs an error result. + * + * @param error The error value to store. + */ + explicit result(E error) : has_value_{false}, error_{error} {} + + /** + * @brief Checks whether the result is a success. + * + * @return true if the result is successful, false otherwise. + */ + explicit operator bool() const { return has_value_; } + + /** + * @brief Gets the stored error. + * + * @return Const reference to the stored error. + * @note Only valid if the result holds an error. + */ + const E& error() const { return error_; } + + private: + bool has_value_{true}; + E error_; +}; + +} // namespace msd + +#endif // MSD_CHANNEL_RESULT_HPP_ diff --git a/tests/channel_test.cpp b/tests/channel_test.cpp index 964a29c..9ad95b8 100644 --- a/tests/channel_test.cpp +++ b/tests/channel_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,79 @@ TEST(ChannelTest, PushByMoveAndFetch) EXPECT_EQ("def", out); } +TEST(ChannelTest, BatchWriteOnUnbufferedChannel) +{ + msd::channel channel{}; + + msd::result result; + std::vector input(100); + std::iota(input.begin(), input.end(), 0); + + result = channel.batch_write(input.cbegin(), input.cend()); + EXPECT_TRUE(result); + EXPECT_EQ(channel.size(), 100); + EXPECT_EQ(input.size(), 100); + + std::vector output(100); + auto iter = output.begin(); + while (!channel.empty()) { + channel >> *iter; + ++iter; + } + for (std::size_t i = 0; i < output.size(); ++i) { + EXPECT_EQ(output[i], i); + } +} + +TEST(ChannelTest, BatchWriteOnBufferedChannel) +{ + msd::channel channel{10}; + + msd::result result; + std::vector input(100); + std::iota(input.begin(), input.end(), 0); + + // Too many + result = channel.batch_write(input.cbegin(), input.cend()); + EXPECT_FALSE(result); + EXPECT_EQ(result.error(), msd::batch_write_error::range_exceeds_capacity); + EXPECT_EQ(channel.size(), 0); + EXPECT_EQ(input.size(), 100); + + // Ok + result = channel.batch_write(std::next(input.begin(), 2), std::next(input.begin(), 7)); + EXPECT_TRUE(result); + EXPECT_EQ(channel.size(), 5); + EXPECT_EQ(input.size(), 100); + + std::vector output(5); + auto iter = output.begin(); + while (!channel.empty()) { + channel >> *iter; + ++iter; + } + for (std::size_t i = 0; i < output.size(); ++i) { + EXPECT_EQ(output[i], i + 2); + } +} + +TEST(ChannelTest, BatchWriteOnClosedChannel) +{ + msd::channel channel{10}; + + msd::result result; + std::vector input(100); + std::iota(input.begin(), input.end(), 0); + + channel.close(); + + result = channel.batch_write(input.cbegin(), input.cend()); + EXPECT_FALSE(result); + EXPECT_EQ(result.error(), msd::batch_write_error::channel_is_closed); + EXPECT_TRUE(channel.empty()); + EXPECT_EQ(input.size(), 100); +} + TEST(ChannelTest, size) { msd::channel channel; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy