diff --git a/include/rosa/support/writer/split_tuple_writer.hpp b/include/rosa/support/writer/split_tuple_writer.hpp index 22e3a35..d98ace2 100644 --- a/include/rosa/support/writer/split_tuple_writer.hpp +++ b/include/rosa/support/writer/split_tuple_writer.hpp @@ -1,554 +1,562 @@ //===-- rosa/support/writer/split_tuple_writer.hpp ---------------*- C++ -*-===/ // // The RoSA Framework // //===----------------------------------------------------------------------===/ /// /// \file rosa/support/writer/split_tuple_writer.hpp /// /// \authors David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2019 /// /// \brief Facilities to split writers of \c std::tuple into writers of /// their elements. /// //===----------------------------------------------------------------------===/ #ifndef ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP #define ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP #include "rosa/support/debug.hpp" #include "rosa/support/sequence.hpp" #include #include #include namespace rosa { namespace writer { /// What to do when splitted element writers got destructed so that some /// incomplete tuples remain in the buffer. enum IncompleteTuplePolicy { Ignore, ///< ignore incomplete tuples, data discarded - Flush ///< flush incomplete tuples, use default value for missing elements + Flush, ///< flush incomplete tuples, use default value for missing elements +#ifndef NDEBUG + DbgFail ///< fail with an assertion, available only in debug build +#endif // !defined NDEBUG }; /// Anonymous namespace providing implementation for splitting tuple writers; /// consider it private. namespace { /// Writes values into a buffer based on an index. /// /// The values are first buffered and flushed out later once a corresponding /// tuple gets complete (i.e., other elementwise writers put the corresponding /// values into the buffer). Since the underlying buffer waits for tuples to /// become complete, the elementwise writers are expected to provide the same /// number of values for the buffer (i.e., completing all tuples that have an /// element). That is, however, not enforced in any ways. /// /// Once all elementwise writers are destructed, the underlying buffer gets /// destructed as well. Should the buffer contain some values, which are /// elements of incomplete tuples, the destructor of the buffer handles the /// situation according to the \c rosa::writer::IncompleteTuplePolicy set when /// the elementwise writers were created by \c rosa::writer::splitTupleWriter(). /// /// \tparam TB buffer to write into /// \tparam I index of the values to write to \p TB /// /// \note \p TB is expected to implemnet an interface matching that of \c /// TupleWriterBuffer. /// /// \note the template class SplittedElementWriter { public: /// Type alias for the values the writer writes. using T = typename TB::template element_type; /// \defgroup SplittedElemenWriterTypedefs Typedefs of /// \c SplittedElementWriter /// /// Useful `typedef`s for writers. /// ///@{ typedef T value_type; ///< Type of values written. typedef T &reference; ///< Reference to the type written ///@} /// Creates a new instance. /// /// \param Buffer buffer \p this object writes values into /// /// \note \p Buffer is captured as a \c std::shared_ptr because it is shared /// among writers for its element positions. SplittedElementWriter(std::shared_ptr Buffer) : Buffer(Buffer) {} /// Tells how many values are still in the buffer. /// /// Values are buffered as long as all elements of their corresponding tuples /// are written into the buffer by other elementwise writers and in turn /// complete tuples written by the buffer itself. This function tells how many /// values are still in the buffer waiting for their containing tuples to be /// complete and written out from the buffer. /// /// \note The function simply calles the corresponding function of \p TB. /// /// \return how many values are still in the buffer. size_t buffered(void) const noexcept { return Buffer->template buffered(); } /// Tells if the last write operation of the buffer was successful. /// /// Values are buffered until their corresponding tuples gets complete. The /// buffer writes complete tuples by the underlying writer. This function /// tells if the buffer successfully has written the latest complete tuple by /// the underlying writer. /// /// Once this function returns \c false, further \c write() calls has no /// effect and the last \c buffered() values will not be written to the /// underlying writer of \c std::tuple. /// /// \note The function simply calles the corresponding function of \p TB. /// /// \return if the last write operation was successful bool good(void) const noexcept { return Buffer->good(); } /// Writes an entry to the buffer. /// /// The function has no effect if an earlier write operation of the buffer has /// failed, that is \c good() returns \c false. /// /// \note The function simply calles the corresponding function of \p TB. /// /// \param V value to write void write(const T &V) { Buffer->template write(V); } private: std::shared_ptr Buffer; ///< Buffer \p this object writes into }; /// Writes an element of a tuple. /// /// \see SplittedElementWriter /// /// \tparam TB type of the buffer \p W writes into /// \tparam I index of the values \p W write to \p TB /// /// \param [in,out] W object to write with /// \param V value to write /// /// \return \p W after writing \p V with it template SplittedElementWriter & operator<<(SplittedElementWriter &W, const typename SplittedElementWriter::value_type &V) { W.write(V); return W; } ///\defgroup TupleBufferContainer Type converter turning a \c std::tuple of ///types into a \c std::tuple of container of those types. /// /// The new type is used for buffering elements of tuples separately. /// ///@{ /// Template declaration. /// /// \tparam T type to convert /// /// \note The template is defined only when \p T is a \c std::tuple. /// /// Usage for a type \c Tuple as:\code /// typename TupleBufferContainer::Type /// \endcode template struct TupleBufferContainer; /// Template definition for \c std::tuple. template struct TupleBufferContainer> { /// The converted type. using Type = std::tuple...>; }; ///@} /// Convenience template type alias for easy use of \c TupleBufferContainer. /// /// Converts a \c std::tuple of types into a \c std::tuple of container of those /// types. /// /// \tparam Tuple type to convert template using tuple_buffer_container_t = typename TupleBufferContainer::Type; /// Buffer for element values for writer of \c std::tuple. /// /// The class can be instantiated with a writer of \c std::tuple and provides /// an interface for writing elements into tuples one by one. /// /// \note The class is utilized by \c SplittedElementWriter. /// /// \tparam TupleWriter the writer type that handles values of \c std::tuple /// /// \note The elements of the written \c std::tuple need to be default /// constructible because of \c rosa::writer::IncompleteTuplePolicy::Flush. /// /// \todo Consider thread safety of the class. template class TupleWriterBuffer { public: /// Type alias for the value type of \p TupleWriter. /// \note The type is expected to be \c std::tuple. using writer_value_type = typename TupleWriter::value_type; /// The number of elements of \c writer_value_type. static constexpr size_t writer_value_size = std::tuple_size_v; /// Template type alias to get element types of \c writer_value_type by /// index. /// \tparam I the index of the element template using element_type = typename std::tuple_element::type; /// Type alias for index sequence for accessing elements of \c /// writer_value_size. using element_idx_seq_t = seq_t; /// Creates a new instance. /// /// \param Writer the writer \p this object uses to write tuples /// \param Policy what to do with incomplete tuples when destructing \p /// this object TupleWriterBuffer(TupleWriter &&Writer, const IncompleteTuplePolicy Policy) noexcept : Writer(Writer), Policy(Policy), Buffer() {} /// Destructor. /// /// Should \p this object has some values in \c Buffer (i.e., incomplete /// tuples), the destructor takes care of them according to \c Policy. /// /// \note The destructor may flush the buffered values if the last write /// operation was successful, that is \c good() returns \c true. ~TupleWriterBuffer(void) noexcept { if (good()) { switch (Policy) { default: ASSERT(false && "Unexpected Policy"); case Ignore: // Do not care about incomplete tuples in the buffer. break; case Flush: // Whatever we have in the buffer, flush it out. flush(); break; +#ifndef NDEBUG + case DbgFail: + ASSERT(empty(element_idx_seq_t()) && "Non-empty buffer"); + break; +#endif // !defined NDEBUG } } } /// Tells how many values are in the buffer for index \p I. /// /// \tparam I the index of the element to check /// /// \return the number of values in buffer for index \p I template size_t buffered(void) const noexcept { return std::get(Buffer).size(); } /// Tells if the last write operation was successful. /// /// Values are buffered until their corresponding tuples gets complete. /// Once a tuple becomes complete, it is written by \c Writer. This function /// tells if writing the latest complete tuple was successful. /// /// Once this function returns \c false, further \c write() calls has no /// effect and the last \c buffered() values will not be written to the /// underlying writer of \c std::tuple. /// /// \return if the last write operation was successful bool good(void) const noexcept { return Writer.good(); } /// Writes an entry to the buffer for index \p I. /// /// The function has no effect if an earlier write operation of the buffer has /// failed, that is \c good() returns \c false. /// /// The value is buffered first and written by \p Writer once its /// corresponding tuple becomes complete (i.e., all other elements of the /// tuple are written into \p this object by corresponding elementwise /// writers). /// /// \tparam I the index of the element to write /// /// \param V value to write template void write(const element_type &V) { ASSERT(!hasCompleteTuple(element_idx_seq_t())); if (good()) { std::get(Buffer).push(V); if (hasCompleteTuple(element_idx_seq_t())) { writeTuple(false); } } ASSERT(!hasCompleteTuple(element_idx_seq_t())); } private: /// Tells whether \c Buffer is completely empty, all elements of it are empty. /// /// \tparam S0 indices for accessing elements of \c std::tuple /// /// \note The parameter provides indices as template parameter \p S0 and its /// actual value is ignored. /// /// \return whether all elements of \c Buffer are empty template bool empty(Seq) const noexcept { return (true && ... && std::get(Buffer).empty()); } /// Tells whether \c Buffer contains at least one complete tuple. /// /// \tparam S0 indices for accessing elements of \c std::tuple /// /// \note The parameter provides indices as template parameter \p S0 and its /// actual value is ignored. /// /// \return whether there is at least one complete tuple in \c Buffer template bool hasCompleteTuple(Seq) const noexcept { return (true && ... && !std::get(Buffer).empty()); } /// Makes sure \c Buffer has one complete tuple in front. /// /// If the tuple in front of \c Buffer is not complete, missing elements are /// default constructed in \c Buffer to complete the tuple. /// /// \tparam S0 indices for accessing elements of \c std::tuple /// /// \note The parameter provides indices as template parameter \p S0 and its /// actual value is ignored. template void makeCompleteTuple(Seq) noexcept { auto addDefaultIfEmpty = [](auto &Queue) { if (Queue.empty()) { Queue.emplace(); // default construct a value in the empty queue. } }; (addDefaultIfEmpty(std::get(Buffer)), ...); } /// Gives the first complete tuple from \c Buffer. /// /// \tparam S0 indices for accessing elements of \c std::tuple /// /// \note The parameter provides indices as template parameter \p S0 and its /// actual value is ignored. /// /// \return the first complete tuple from \c Buffer /// /// \pre There is at least one complete tuple in \c Buffer:\code /// hasCompleteTuple(element_idx_seq_t()) /// \endcode template writer_value_type front(Seq) const noexcept { ASSERT(hasCompleteTuple(element_idx_seq_t())); return {std::get(Buffer).front()...}; } /// Removes the first complete tuple from \c Buffer. /// /// \tparam S0 indices for accessing elements of \c std::tuple /// /// \note The parameter provides indices as template parameter \p S0 and its /// actual value is ignored. /// /// \pre There is at least one complete tuple in \c Buffer:\code /// hasCompleteTuple(element_idx_seq_t()) /// \endcode template void pop(Seq) noexcept { ASSERT(hasCompleteTuple(element_idx_seq_t())); (std::get(Buffer).pop(), ...); } /// Takes the first tuple from \c Buffer and writes it with \c Writer. /// /// \c Buffer need to contain a complete tuple to write it with \c Writer. The /// function makes sure that the tuple in front of \c Buffer is complete if \p /// MakeComplete is true. The tuple in fron of \c Buffer are expected to be /// complete otherwise. /// /// \param MakeComplete whether to make the first tuple complete /// /// \pre There is at least one complete tuple in \c Buffer if not \p /// MakeComplete: \code /// MakeComplete || hasCompleteTuple(element_idx_seq_t()) /// \endcode void writeTuple(const bool MakeComplete) noexcept { if (MakeComplete) { makeCompleteTuple(element_idx_seq_t()); } ASSERT(hasCompleteTuple(element_idx_seq_t())); Writer.write(front(element_idx_seq_t())); pop(element_idx_seq_t()); } /// Empties \c Buffer by writing out all tuples from it so that missing /// elements of incomplete tuples are extended with default constructed /// values. void flush(void) noexcept { while (!empty(element_idx_seq_t())) { ASSERT(!hasCompleteTuple(element_idx_seq_t())); // Sanity check. writeTuple(true); } } TupleWriter Writer; ///< The splitted writer const IncompleteTuplePolicy Policy; ///< what to do with incomplete tuples ///< when destructing \p this object tuple_buffer_container_t Buffer; ///< Container for elementwise buffering }; /// Template type alias for writer of an element based on buffered writer of /// \c std::tuple. /// /// The alias utilizes \c SplittedElementWriter for writing element values by /// \p TB. /// /// \tparam TB buffer to write into /// \tparam I index of the values to write to \p TB template using element_writer_t = SplittedElementWriter; ///\defgroup ElementWriters Type converter turning a buffer of \c std::tuple ///into a \c std::tuple of corresponding \c element_writer_t. /// ///@{ /// Template declaration. /// /// \tparam TB buffer to write into /// \tparam S type providing indices for accessing elements of \c std::tuple /// /// \note \p TB is expected to implement an interface matching that of \c /// TupleWriterBuffer. /// /// \note The template is defined only when \p S is a \c rosa::Seq. /// /// Usage for a proper buffer type \c TB:\code /// typename ElementIteratorWriters::Type /// \endcode template struct ElementWriters; /// Template definition. template struct ElementWriters> { /// The converted type. using Type = std::tuple...>; }; ///@} /// Convenience template type alias for easy use of \c ElementIteratorWriters. /// /// Converts a buffer of \c std::tuple into a \c std::tuple of corresponding \c /// element_writer_t. /// /// \tparam TB buffer to write into /// /// \note \p TB is expected to implement an interface matching that of \c /// TupleWriterBuffer. template using element_writers_t = typename ElementWriters::Type; /// Template type alias for turning a writer of \c std::tuple into /// corresponding \c element_writers_t. /// /// The alias utilizes \c TupleWriterBuffer for buffering values before writing /// them by \p TupleWriter. The elementwise writers are \c /// SplittedElementWriter. /// /// \tparam TupleWriter writer of \c std::tuple template using splitted_tuple_writers_t = element_writers_t>; /// Creates elementwise writers for a writer of \c std::tuple. /// /// \note Implementation for \c rosa::iterator::splitTupleWriter. /// /// \tparam TupleWriter writer of \c std::tuple /// \tparam S0 indices for accessing elements of \c std::tuple /// /// \param Writer the writer to write tuples with /// \param Policy how to handle incomplete tuples when destructing the /// underlying buffer /// /// \note The last parameter provides indices as template parameter \p S0 and /// its actual value is ignored. /// /// \return \c std::tuple of elementwise writers corresponding to \p Writer template splitted_tuple_writers_t splitTupleWriterImpl(TupleWriter &&Writer, const IncompleteTuplePolicy Policy, Seq) noexcept { using TB = TupleWriterBuffer; // Create a buffer in shared pointer. The buffer will be destructed once // all corresponding element writers (created below) have been destructed. auto Buffer = std::make_shared(std::move(Writer), Policy); return {element_writer_t(Buffer)...}; } } // End namespace /// Creates elementwise writers for a writer of \c std::tuple. /// /// \note The implementation utilizes \c splitTupleWriterImpl. /// /// Obtain elementwise writers for a writer \p Writer of type \c TupleWriter of /// \c std::tuple as \code /// auto [T1Writer, T2Writer, T3Writer] = splitTupleWriter(std::move(Writer), /// Ignore); /// \endcode /// /// The function returns a tuple of writers, which can be assigned to variables /// using structured binding. /// /// The function moves its first argument (i.e., \p Writer cannot be used /// directly after splitting it). If the first argument is a variable, it needs /// to be moved explicitly by \c std::move() as in the example. /// /// While a tuple could be written with \p Writer directly as \code /// Writer << std::make_tuple(T1(), T2(), T3()); /// \endcode The same tuple is written with splitted writers as \code /// T1Writer << T1(); /// T2Writer << T2(); /// T3Writer << T3(); /// \endcode /// /// \tparam TupleWriter writer of \c std::tuple /// /// \note The elements of the written \c std::tuple need to be default /// constructible because of \c rosa::writer::IncompleteTuplePolicy::Flush. /// /// \param Writer the writer to write tuples with /// \param Policy what to do with incomplete tuples when destructing the /// underlying buffer /// /// \return \c std::tuple of elementwise writers corresponding to \p Writer template splitted_tuple_writers_t splitTupleWriter(TupleWriter &&Writer, const IncompleteTuplePolicy Policy) noexcept { return splitTupleWriterImpl( std::move(Writer), Policy, seq_t>()); } } // End namespace writer } // End namespace rosa #endif // ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP