diff --git a/src/picross/src/line_alternatives.cpp b/src/picross/src/line_alternatives.cpp index d56343e..d6fa7e6 100644 --- a/src/picross/src/line_alternatives.cpp +++ b/src/picross/src/line_alternatives.cpp @@ -399,9 +399,9 @@ namespace { } private: - LineSpanW m_all_reduced_lines; - stdutils::span m_nb_alternatives; - stdutils::span m_recorded; + LineSpanW m_all_reduced_lines; + stdutils::Span m_nb_alternatives; + stdutils::Span m_recorded; unsigned int m_max_line_length; }; diff --git a/src/stdutils/include/stdutils/chrono.h b/src/stdutils/include/stdutils/chrono.h index 18e95c8..be1f85d 100644 --- a/src/stdutils/include/stdutils/chrono.h +++ b/src/stdutils/include/stdutils/chrono.h @@ -7,6 +7,15 @@ namespace stdutils { namespace chrono { +// For example to measure a duration in milliseconds: +// +// std::chrono::duration duration; +// { +// stdutils::chrono::DurationMeas meas(duration); +// // Do something +// } +// float duration_ms = time.count(); +// template class DurationMeas { diff --git a/src/stdutils/include/stdutils/io.h b/src/stdutils/include/stdutils/io.h index d39651b..7d29b92 100644 --- a/src/stdutils/include/stdutils/io.h +++ b/src/stdutils/include/stdutils/io.h @@ -6,8 +6,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -31,6 +33,7 @@ struct Severity static constexpr SeverityCode EXCPT = -1; static constexpr SeverityCode ERR = 1; static constexpr SeverityCode WARN = 2; + static constexpr SeverityCode INFO = 3; }; std::string_view str_severity_code(SeverityCode code); @@ -39,6 +42,39 @@ using ErrorMessage = std::string_view; using ErrorHandler = std::function; +/** + * Floating point IO precision + * + * Set the output stream precision so that the round-trip fp -> text -> fp is exact. + * Return the original precision of the stream. + */ +template +int accurate_fp_precision(std::basic_ostream>& out); + +/** + * Floating point IO precision + * + * Set the output stream precision. If passed a negative value, set to the same precision as stdutlls::accurate_fp_precision + * Return the original precision of the stream. + */ +template +int fp_precision(std::basic_ostream>& out, int precision = -1); + +/** + * RAII class to save/restore stream format for numerical values + */ +template +class SaveNumericFormat +{ +public: + SaveNumericFormat(std::basic_ostream>& out); + ~SaveNumericFormat(); +private: + std::basic_ostream>& m_out; + std::ios_base::fmtflags m_flags; + std::streamsize m_precision; +}; + /** * Pass a file to a parser of std::basic_istream */ @@ -46,7 +82,21 @@ template using StreamParser = std::function>&, const stdutils::io::ErrorHandler&)>; template -Ret open_and_parse_file(const std::filesystem::path& filepath, const StreamParser& stream_parser, const stdutils::io::ErrorHandler& err_handler) noexcept; +Ret open_and_parse_txt_file(const std::filesystem::path& filepath, const StreamParser& stream_parser, const stdutils::io::ErrorHandler& err_handler) noexcept; +template +Ret open_and_parse_bin_file(const std::filesystem::path& filepath, const StreamParser& stream_parser, const stdutils::io::ErrorHandler& err_handler) noexcept; + + +/** + * Save a file with a writer to std::basic_ostream + */ +template +using StreamWriter = std::function>&, const Obj&, const stdutils::io::ErrorHandler&)>; + +template +void save_txt_file(const std::filesystem::path& filepath, const StreamWriter& stream_writer, const Obj& obj, const stdutils::io::ErrorHandler& err_handler) noexcept; +template +void save_bin_file(const std::filesystem::path& filepath, const StreamWriter& stream_writer, const Obj& obj, const stdutils::io::ErrorHandler& err_handler) noexcept; /** * LineStream: A wrapper around std::getline to count line nb @@ -114,19 +164,60 @@ using SkipLineStream = Basic_SkipLineStream; template std::size_t countlines(std::basic_istream>& istream); + // // -// IMPLEMENTATION +// Implementation // // + +template +int accurate_fp_precision(std::basic_ostream>& out) +{ + // The precision max_digits10 (9 for float, 17 for double) ensures the round-trip "fp -> text -> fp" will be exact. + constexpr int max_fp_digits = std::numeric_limits::max_digits10; + const int initial_fp_digits = static_cast(out.precision()); + out << std::setprecision(max_fp_digits); + return initial_fp_digits; +} + +template +int fp_precision(std::basic_ostream>& out, int precision) +{ + const int initial_fp_digits = static_cast(out.precision()); + if (precision < 0) + out << std::setprecision(std::numeric_limits::max_digits10); + else + out << std::setprecision(precision); + return initial_fp_digits; +} + +template +SaveNumericFormat::SaveNumericFormat(std::basic_ostream>& out) + : m_out(out) + , m_flags(out.flags()) + , m_precision(out.precision()) +{ } + +template +SaveNumericFormat::~SaveNumericFormat() +{ + m_out.flags(m_flags); + m_out.precision(m_precision); +} + +namespace details { + template -Ret open_and_parse_file(const std::filesystem::path& filepath, const StreamParser& stream_parser, const stdutils::io::ErrorHandler& err_handler) noexcept +Ret open_and_parse_file(const std::filesystem::path& filepath, bool binary, const StreamParser& stream_parser, const stdutils::io::ErrorHandler& err_handler) noexcept { static_assert(std::is_nothrow_default_constructible_v); try { - std::basic_ifstream inputstream(filepath); + std::ios_base::openmode mode = std::ios_base::in; + if (binary) { mode |= std::ios_base::binary; } + std::basic_ifstream inputstream(filepath, mode); if (inputstream.is_open()) { return stream_parser(inputstream, err_handler); @@ -141,12 +232,69 @@ Ret open_and_parse_file(const std::filesystem::path& filepath, const StreamParse catch(const std::exception& e) { std::stringstream oss; - oss << "Exception: " << e.what(); + oss << "stdutils::io::details::open_and_parse_file(" << filepath << ", " << (binary ? "BIN" : "TXT") << "): " << e.what(); err_handler(stdutils::io::Severity::EXCPT, oss.str()); } return Ret(); } +template +void save_file(const std::filesystem::path& filepath, bool binary, const StreamWriter& stream_writer, const Obj& obj, const stdutils::io::ErrorHandler& err_handler) noexcept +{ + try + { + std::ios_base::openmode mode = std::ios_base::out; + if (binary) { mode |= std::ios_base::binary; } + std::basic_ofstream outputstream(filepath, mode); + if (outputstream.is_open()) + { + stream_writer(outputstream, obj, err_handler); + } + else + { + std::stringstream oss; + oss << "Cannot open file " << filepath; + err_handler(stdutils::io::Severity::FATAL, oss.str()); + } + } + catch(const std::exception& e) + { + std::stringstream oss; + oss << "stdutils::io::details::save_file(" << filepath << ", " << (binary ? "BIN" : "TXT") << "): " << e.what(); + err_handler(stdutils::io::Severity::EXCPT, oss.str()); + } +} + +} // namespace details + +template +Ret open_and_parse_txt_file(const std::filesystem::path& filepath, const StreamParser& stream_parser, const stdutils::io::ErrorHandler& err_handler) noexcept +{ + const bool binary_file = false; + return details::open_and_parse_file(filepath, binary_file, stream_parser, err_handler); +} + +template +Ret open_and_parse_bin_file(const std::filesystem::path& filepath, const StreamParser& stream_parser, const stdutils::io::ErrorHandler& err_handler) noexcept +{ + const bool binary_file = true; + return details::open_and_parse_file(filepath, binary_file, stream_parser, err_handler); +} + +template +void save_txt_file(const std::filesystem::path& filepath, const StreamWriter& stream_writer, const Obj& obj, const stdutils::io::ErrorHandler& err_handler) noexcept +{ + const bool binary_file = false; + details::save_file(filepath, binary_file, stream_writer, obj, err_handler); +} + +template +void save_bin_file(const std::filesystem::path& filepath, const StreamWriter& stream_writer, const Obj& obj, const stdutils::io::ErrorHandler& err_handler) noexcept +{ + const bool binary_file = true; + details::save_file(filepath, binary_file, stream_writer, obj, err_handler); +} + template Basic_LineStream::Basic_LineStream(stream_type& source) : m_stream(source) diff --git a/src/stdutils/include/stdutils/macros.h b/src/stdutils/include/stdutils/macros.h index 1a9ee97..b0b092a 100644 --- a/src/stdutils/include/stdutils/macros.h +++ b/src/stdutils/include/stdutils/macros.h @@ -1,3 +1,4 @@ #pragma once #define UNUSED(x) (void)(x) +#define IGNORE_RETURN (void) diff --git a/src/stdutils/include/stdutils/span.h b/src/stdutils/include/stdutils/span.h index a1ca3e8..3bb06d3 100644 --- a/src/stdutils/include/stdutils/span.h +++ b/src/stdutils/include/stdutils/span.h @@ -2,6 +2,7 @@ // This code is distributed under the terms of the MIT License #pragma once +#include #include #include #include @@ -14,65 +15,101 @@ inline constexpr std::size_t dyn_extent = std::numeric_limits::max( // Imitation of std::span in C++20 template -class span {}; +class Span {}; template -class span +class Span { public: - span(T* ptr, std::size_t size) noexcept : m_ptr(ptr), m_size(size) { assert(m_ptr); } - span(const span&) noexcept = default; - span(span&&) noexcept = default; - span& operator=(const span&) noexcept = default; - span& operator=(span&&) noexcept = default; + using element_type = T; + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + +public: + Span() noexcept : m_ptr(nullptr), m_size(0) { } + Span(T* ptr, std::size_t size) noexcept : m_ptr(ptr), m_size(size) { assert(m_ptr); } + Span(const Span&) noexcept = default; + Span(Span&&) noexcept = default; + Span& operator=(const Span&) noexcept = default; + Span& operator=(Span&&) noexcept = default; // For qualification conversions (e.g. non-const T to const T) template - span(const span& other) noexcept : m_ptr(other.begin()), m_size(other.size()) { assert(m_ptr); } + Span(const Span& other) noexcept : m_ptr(other.data()), m_size(other.size()) { assert(m_ptr); } - std::size_t size() const { return m_size; } + std::size_t size() const noexcept { return m_size; } + bool empty() const noexcept { return m_size == 0 || m_ptr == nullptr; } - T* data() { return m_ptr; } - T* begin() { return m_ptr; } - T* end() { return m_ptr + m_size; } - const T* data() const { return m_ptr; } - const T* begin() const { return m_ptr; } - const T* end() const { return m_ptr + m_size; } + pointer data() noexcept { return m_ptr; } + pointer begin() noexcept { return m_ptr; } + pointer end() noexcept { return m_ptr + m_size; } + const_pointer data() const noexcept { return m_ptr; } + const_pointer begin() const noexcept { return m_ptr; } + const_pointer end() const noexcept { return m_ptr + m_size; } - T& operator[](std::size_t idx) { assert(idx < m_size); return *(m_ptr + idx); } - const T& operator[](std::size_t idx) const { assert(idx < m_size); return *(m_ptr + idx); } + reference operator[](std::size_t idx) { assert(idx < m_size); return *(m_ptr + idx); } + const_reference operator[](std::size_t idx) const { assert(idx < m_size); return *(m_ptr + idx); } private: T* m_ptr; std::size_t m_size; }; +template +Span make_span(C& container) +{ + using T = typename C::value_type; + return Span(container.data(), container.size()); +} + +template +Span make_const_span(const C& container) +{ + using T = typename C::value_type; + return Span(container.data(), container.size()); +} + template -class span> +class Span> { - using this_type = span>; + using this_type = Span>; + static_assert(Sz > 0); + +public: + using element_type = T; + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + public: - explicit span(T* ptr) noexcept : m_ptr(ptr) { assert(m_ptr); } - span(const this_type&) noexcept = default; - span(this_type&&) noexcept = default; + explicit Span(T* ptr) noexcept : m_ptr(ptr) { assert(m_ptr); } + explicit Span(std::array& arr) noexcept : m_ptr(arr.data()) { assert(m_ptr); } + explicit Span(const std::array, Sz>& arr) noexcept : m_ptr(arr.data()) { assert(m_ptr); } + Span(const this_type&) noexcept = default; + Span(this_type&&) noexcept = default; this_type& operator=(const this_type&) noexcept = default; this_type& operator=(this_type&&) noexcept = default; // For qualification conversions (e.g. non-const T to const T) template - span(const span& other) noexcept : m_ptr(other.begin()) { assert(m_ptr); } + Span(const Span& other) noexcept : m_ptr(other.begin()) { assert(m_ptr); } - constexpr std::size_t size() const { return Sz; } + constexpr std::size_t size() const noexcept { return Sz; } - T* data() { return m_ptr; } - T* begin() { return m_ptr; } - T* end() { return m_ptr + Sz; } - const T* data() const { return m_ptr; } - const T* begin() const { return m_ptr; } - const T* end() const { return m_ptr + Sz; } + pointer data() noexcept { return m_ptr; } + pointer begin() noexcept { return m_ptr; } + pointer end() noexcept { return m_ptr + Sz; } + const_pointer data() const noexcept { return m_ptr; } + const_pointer begin() const noexcept { return m_ptr; } + const_pointer end() const noexcept { return m_ptr + Sz; } - T& operator[](std::size_t idx) { assert(idx < Sz); return *(m_ptr + idx); } - const T& operator[](std::size_t idx) const { assert(idx < Sz); return *(m_ptr + idx); } + reference operator[](std::size_t idx) { assert(idx < Sz); return *(m_ptr + idx); } + const_reference operator[](std::size_t idx) const { assert(idx < Sz); return *(m_ptr + idx); } private: T* m_ptr; diff --git a/src/stdutils/include/stdutils/string.h b/src/stdutils/include/stdutils/string.h index 44911a8..ca2e1d9 100644 --- a/src/stdutils/include/stdutils/string.h +++ b/src/stdutils/include/stdutils/string.h @@ -26,19 +26,22 @@ std::string capitalize(const std::string& in); * out << indent(2); // Output 2 indentations */ template -class BasicIndent : private std::basic_string +class BasicIndent { public: class Multi; - BasicIndent(std::size_t count, CharT ch = ' ') : std::basic_string(count, ch) {} + BasicIndent(std::size_t count, CharT ch = ' ') : m_str(count, ch) {} Multi operator()(std::size_t factor) const { return Multi(*this, factor); } friend std::basic_ostream& operator<<(std::basic_ostream& out, const BasicIndent& indent) { - return out << static_cast&>(indent); + return out << indent.m_str; } + +private: + std::basic_string m_str; }; template diff --git a/src/stdutils/src/io.cpp b/src/stdutils/src/io.cpp index 437bf0f..ee7ac70 100644 --- a/src/stdutils/src/io.cpp +++ b/src/stdutils/src/io.cpp @@ -17,13 +17,17 @@ std::string_view str_severity_code(SeverityCode code) { return "EXCPT"; } + else if (code == Severity::ERR) + { + return "ERROR"; + } else if (code == Severity::WARN) { return "WARNING"; } - else if (code == Severity::ERR) + else if (code == Severity::INFO) { - return "ERROR"; + return "INFO"; } else { diff --git a/src/tests/stdutils/src/test_io.cpp b/src/tests/stdutils/src/test_io.cpp index 324c53f..260889e 100644 --- a/src/tests/stdutils/src/test_io.cpp +++ b/src/tests/stdutils/src/test_io.cpp @@ -1,3 +1,5 @@ +// Copyright (c) 2023 Pierre DEJOUE +// This code is distributed under the terms of the MIT License #include #include diff --git a/src/tests/stdutils/src/test_span.cpp b/src/tests/stdutils/src/test_span.cpp index b8e68d9..41f3b38 100644 --- a/src/tests/stdutils/src/test_span.cpp +++ b/src/tests/stdutils/src/test_span.cpp @@ -1,33 +1,92 @@ +// Copyright (c) 2023 Pierre DEJOUE +// This code is distributed under the terms of the MIT License #include #include +#include +#include #include +TEST_CASE("Trivial dynamic extent span", "[span]") +{ + stdutils::Span span; + + CHECK(span.size() == 0); + CHECK(span.empty() == true); + CHECK(span.data() == nullptr); +} + TEST_CASE("Dynamic extent span to span", "[span]") { std::vector test_vect { 0, 2, 3, 4 }; - auto span = stdutils::span(test_vect.data(), test_vect.size()); + auto span = stdutils::Span(test_vect.data(), test_vect.size()); span[0] = 1; CHECK(test_vect[0] == 1); - - auto const_span = stdutils::span(span); + auto const_span = stdutils::Span(span); CHECK(const_span[0] == 1); CHECK(const_span.size() == test_vect.size()); } +TEST_CASE("Dynamic extent span from containers with make_span", "[span]") +{ + std::vector test_vect { 0, 2, 3, 4 }; + + auto span = stdutils::make_span(test_vect); + REQUIRE(span.size() == test_vect.size()); + span[0] = 1; + CHECK(span[0] == 1); + + auto const_span = stdutils::make_const_span(test_vect); + static_assert(std::is_same_v); + REQUIRE(const_span.size() == test_vect.size()); + CHECK(const_span[0] == 1); +} + TEST_CASE("Static extent span to span", "[span]") { std::vector test_vect { 1, 2, 3, 4 }; - auto span = stdutils::span(test_vect.data() + 2); + auto span = stdutils::Span(test_vect.data() + 2); REQUIRE(span.size() == 2); span[0] = 0; CHECK(test_vect[2] == 0); - auto const_span = stdutils::span(span); + auto const_span = stdutils::Span(span); REQUIRE(const_span.size() == 2); CHECK(const_span[0] == 0); CHECK(const_span[1] == 4); } + +TEST_CASE("Static extent span on arrays", "[span]") +{ + std::array test_arr { 1, 2, 3, 4 }; + const std::array test_const_arr { 5, 6, 7 }; + + auto span = stdutils::Span(test_arr); + REQUIRE(span.size() == 4); + span[2] = 0; + CHECK(span[2] == 0); + CHECK(test_arr[2] == 0); + + auto const_span = stdutils::Span(test_const_arr); + REQUIRE(const_span.size() == 3); + CHECK(const_span[0] == 5); + CHECK(const_span[1] == 6); +} + +TEST_CASE("Dynamic extent span on arrays", "[span]") +{ + std::array test_arr { 0, 2, 3, 4 }; + + auto span = stdutils::make_span(test_arr); + REQUIRE(span.size() == test_arr.size()); + span[0] = 1; + CHECK(span[0] == 1); + + auto const_span = stdutils::make_const_span(test_arr); + static_assert(std::is_same_v); + REQUIRE(const_span.size() == test_arr.size()); + CHECK(const_span[0] == 1); +}