From 700d37b73c567c38e8f1d8bdb7aacb1cc95a6a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Fri, 8 May 2026 19:54:04 +0200 Subject: [PATCH 01/21] migrate to c++23 * updated badges * enhanced fe/format.h --- CMakeLists.txt | 2 +- docs/README.md | 9 +++-- include/fe/format.h | 91 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21e46a7..435804c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ target_sources(fe include/fe/utf8.h ) target_include_directories(fe INTERFACE $) -target_compile_features(fe INTERFACE cxx_std_20) +target_compile_features(fe INTERFACE cxx_std_23) if(MSVC) target_compile_definitions(fe INTERFACE _CTYPE_DISABLE_MACROS) diff --git a/docs/README.md b/docs/README.md index 0b4d1d2..c258111 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,15 +1,18 @@ # FE +[![GitHub Release](https://img.shields.io/github/v/release/leissa/fe?style=flat-square&logo=starship)](https://github.com/leissa/fe/releases) [![Linux](https://img.shields.io/github/actions/workflow/status/leissa/fe/linux.yml?style=flat-square&logo=linux&label=linux&logoColor=white&branch=main)](https://github.com/leissa/fe/actions/workflows/linux.yml) [![Windows](https://img.shields.io/github/actions/workflow/status/leissa/fe/windows.yml?style=flat-square&label=⊞%20windows&branch=main)](https://github.com/leissa/fe/actions/workflows/windows.yml) [![macOS](https://img.shields.io/github/actions/workflow/status/leissa/fe/macos.yml?style=flat-square&logo=apple&label=macos&branch=main)](https://github.com/leissa/fe/actions/workflows/macos.yml) [![Doxygen](https://img.shields.io/github/actions/workflow/status/leissa/fe/doxygen.yml?style=flat-square&logo=gitbook&logoColor=white&label=doxygen&branch=main)](https://github.com/leissa/fe/actions/workflows/doxygen.yml) -[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=opensourceinitiative&logoColor=white)](../LICENSE) -![GitHub Release](https://img.shields.io/github/v/release/leissa/fe?style=flat-square&logo=starship) +[![Documentation](https://img.shields.io/badge/docs-master-green?style=flat-square&logo=gitbook&logoColor=white)](https://leissa.github.io/fe) + +[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue?style=flat-square&logo=cplusplus)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=opensourceinitiative&logoColor=white&color=yellowgreen)](../LICENSE) [TOC] -**FE** is a header-only C++20 toolkit for building handwritten compiler and interpreter frontends. +**FE** is a header-only C++23 toolkit for building handwritten compiler and interpreter frontends. Rather than generating lexers or parsers for you, FE focuses on the infrastructure that every frontend needs anyway: source locations, diagnostics, interning, parsing support, and efficient memory management. The goal is simple: keep handwritten frontends lightweight, explicit, and pleasant to maintain. diff --git a/include/fe/format.h b/include/fe/format.h index b4feeae..5263a54 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -1,6 +1,11 @@ #pragma once +#include #include +#include +#include +#include +#include #include "fe/loc.h" #include "fe/utf8.h" @@ -19,11 +24,7 @@ struct basic_ostream_formatter : std::formatter, Ch O format(const T& value, std::basic_format_context& ctx) const { std::basic_stringstream ss; ss << value; -#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 170000 - return std::formatter, Char>::format(ss.str(), ctx); -#else return std::formatter, Char>::format(ss.view(), ctx); -#endif } }; @@ -85,14 +86,96 @@ class Tab { return os; } + /// @name Formatted Output + /// Wrap `std::format` to prefix the formatted string with the current indentation. + ///@{ + // clang-format off + template std::ostream& print (std::ostream& os, std::format_string fmt, Args&&... args) const { return os << *this << std::format(fmt, std::forward(args)...); } + template std::ostream& println(std::ostream& os, std::format_string fmt, Args&&... args) const { return (os << *this << std::format(fmt, std::forward(args)...)) << std::endl; } + template std::ostream& lnprint(std::ostream& os, std::format_string fmt, Args&&... args) const { return os << std::endl << *this << std::format(fmt, std::forward(args)...); } + // clang-format on + ///@} + private: std::string_view tab_; size_t indent_ = 0; }; +/// Wrap a callable `f(std::ostream&) -> std::ostream&` so it streams via `operator<<` and `std::format`. +/// Useful for inline ad-hoc formatting: +/// ``` +/// auto greet = fe::StreamFn{[](std::ostream& os) -> std::ostream& { return os << "hi"; }}; +/// std::cout << greet; +/// std::format("{}", greet); +/// ``` +template + requires std::invocable +class StreamFn { +public: + constexpr StreamFn(F f) + : f_(std::move(f)) {} + + friend std::ostream& operator<<(std::ostream& os, const StreamFn& s) { + if constexpr (std::is_void_v>) + return std::invoke(s.f_, os), os; + else + return std::invoke(s.f_, os); + } + +private: + F f_; +}; + +template StreamFn(F) -> StreamFn; + +/// Join elements of @p range with @p sep. +/// Use as a `std::format` argument: `std::format("{}", fe::join(v, ", "))`. +/// The @p range must outlive the returned object. +template +class Join { +public: + Join(const R& range, std::string_view sep) + : range_(range) + , sep_(sep) {} + + const R& range() const { return range_; } + std::string_view sep() const { return sep_; } + +private: + const R& range_; + std::string_view sep_; +}; + +template Join(const R&, std::string_view) -> Join; + +template +Join join(const R& range, std::string_view sep) { + return Join(range, sep); +} + } // namespace fe #ifndef DOXYGEN +template +struct std::formatter> : fe::ostream_formatter {}; + +template +struct std::formatter> { + constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } + + template + auto format(const fe::Join& j, FormatContext& ctx) const { + auto out = ctx.out(); + bool first = true; + for (const auto& elem : j.range()) { + if (!first) out = std::format_to(out, "{}", j.sep()); + out = std::format_to(out, "{}", elem); + first = false; + } + return out; + } +}; + // clang-format off template<> struct std::formatter : fe::ostream_formatter {}; template<> struct std::formatter : fe::ostream_formatter {}; From 219c741d8537fc681269cab6942a724473ee5ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Fri, 8 May 2026 20:04:52 +0200 Subject: [PATCH 02/21] removed duplicates from fe::Tab --- include/fe/format.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index a5dc5f0..004a9cf 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -54,18 +54,6 @@ class Tab { constexpr std::string_view tab() const noexcept { return tab_; } ///@} - /// @name Setters - ///@{ - Tab& operator=(size_t indent) { - indent_ = indent; - return *this; - } - Tab& operator=(std::string tab) { - tab_ = tab; - return *this; - } - ///@} - // clang-format off /// @name Creates a new Tab ///@{ From 2458048de295ea27abd0cb72105b90663edbb6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Fri, 8 May 2026 20:07:55 +0200 Subject: [PATCH 03/21] msvc fix --- include/fe/format.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index 004a9cf..cdf0847 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -57,10 +57,10 @@ class Tab { // clang-format off /// @name Creates a new Tab ///@{ - [[nodiscard]] constexpr Tab operator++(int) noexcept { return {tab_, indent_++}; } - [[nodiscard]] constexpr Tab operator--(int) noexcept { assert(indent_ > 0); return {tab_, indent_--}; } - [[nodiscard]] constexpr Tab operator+(size_t indent) const noexcept { return {tab_, indent_ + indent}; } - [[nodiscard]] constexpr Tab operator-(size_t indent) const noexcept { assert(indent_ > 0); return {tab_, indent_ - indent}; } + [[nodiscard]] Tab operator++(int) noexcept { return {tab_, indent_++}; } + [[nodiscard]] Tab operator--(int) noexcept { assert(indent_ > 0); return {tab_, indent_--}; } + [[nodiscard]] Tab operator+(size_t indent) const noexcept { return {tab_, indent_ + indent}; } + [[nodiscard]] Tab operator-(size_t indent) const noexcept { assert(indent_ > 0); return {tab_, indent_ - indent}; } ///@} /// @name Modifies this Tab From b419d96d89037120bccc98dafaeba85ac2f40e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Fri, 8 May 2026 22:23:07 +0200 Subject: [PATCH 04/21] added operator<<(std::ostream&, const Join&) --- include/fe/format.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/include/fe/format.h b/include/fe/format.h index cdf0847..240c882 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -123,7 +123,7 @@ class StreamFn { template StreamFn(F) -> StreamFn; /// Join elements of @p range with @p sep. -/// Use as a `std::format` argument: `std::format("{}", fe::join(v, ", "))`. +/// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::join(v, ", "))`. /// The @p range must outlive the returned object. template class Join { @@ -135,6 +135,14 @@ class Join { const R& range() const { return range_; } std::string_view sep() const { return sep_; } + friend std::ostream& operator<<(std::ostream& os, const Join& j) { + for (std::string_view curr_sep; const auto& elem : j.range_) { + os << curr_sep << elem; + curr_sep = j.sep_; + } + return os; + } + private: const R& range_; std::string_view sep_; From ba41eb86cefb2312b97eb534288f332610086bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 09:43:51 +0200 Subject: [PATCH 05/21] removed err(ln)/out(ln) in favor of std::print(ln) --- include/fe/format.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index 240c882..472e5d1 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -30,16 +30,6 @@ struct basic_ostream_formatter : std::formatter, Ch using ostream_formatter = basic_ostream_formatter; -/// @name out/outln/err/errln -/// Print to `std::cout`/`std::cerr` via `std::format`; the `*ln` variants conclude with `std::endl`. -///@{ -// clang-format off -template void err (std::format_string fmt, Args&&... args) { std::cerr << std::format(fmt, std::forward(args)...); } -template void out (std::format_string fmt, Args&&... args) { std::cout << std::format(fmt, std::forward(args)...); } -template void errln(std::format_string fmt, Args&&... args) { std::cerr << std::format(fmt, std::forward(args)...) << std::endl; } -template void outln(std::format_string fmt, Args&&... args) { std::cout << std::format(fmt, std::forward(args)...) << std::endl; } -// clang-format on - /// Keeps track of indentation level during output class Tab { public: From f12218e74dbe3b5c77a6dca71f308569a4784fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 09:44:09 +0200 Subject: [PATCH 06/21] added assertf --- include/fe/format.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/fe/format.h b/include/fe/format.h index 472e5d1..a0946d9 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -168,6 +168,22 @@ struct std::formatter> { } }; +#ifdef NDEBUG +# define assertf(condition, ...) \ + do { \ + (void)sizeof(condition); \ + } while (false) +#else +# define assertf(condition, ...) \ + do { \ + if (!(condition)) { \ + std::println(std::cerr, "{}:{}: assertion: ", __FILE__, __LINE__); \ + std::println(std::cerr, __VA_ARGS__); \ + fe::breakpoint(); \ + } \ + } while (false) +#endif + // clang-format off template<> struct std::formatter : fe::ostream_formatter {}; template<> struct std::formatter : fe::ostream_formatter {}; From 23f1fbd107c9e008fd53dced4a39fb6522ea84c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 12:47:21 +0200 Subject: [PATCH 07/21] cleanup --- include/fe/format.h | 70 ++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index a0946d9..7dd98d2 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -1,10 +1,12 @@ #pragma once #include + #include #include #include #include +#include #include #include "fe/loc.h" @@ -20,11 +22,11 @@ namespace fe { /// @sa [Stack Overflow](https://stackoverflow.com/a/75738462). template struct basic_ostream_formatter : std::formatter, Char> { - template - O format(const T& value, std::basic_format_context& ctx) const { + template + auto format(T const& value, FormatContext& ctx) const { std::basic_stringstream ss; ss << value; - return std::formatter, Char>::format(ss.view(), ctx); + return std::formatter, Char>::format(ss.str(), ctx); } }; @@ -33,11 +35,16 @@ using ostream_formatter = basic_ostream_formatter; /// Keeps track of indentation level during output class Tab { public: + /// @name Construction + ///@{ Tab(const Tab&) = default; Tab(std::string_view tab = {"\t"}, size_t indent = 0) : tab_(tab) , indent_(indent) {} + static Tab spaces() { return Tab(std::string_view(" ")); } + ///@} + /// @name Getters ///@{ constexpr size_t indent() const noexcept { return indent_; } @@ -59,8 +66,6 @@ class Tab { constexpr Tab& operator--() noexcept { assert(indent_ > 0); --indent_; return *this; } constexpr Tab& operator+=(size_t indent) noexcept { indent_ += indent; return *this; } constexpr Tab& operator-=(size_t indent) noexcept { assert(indent_ > 0); indent_ -= indent; return *this; } - constexpr Tab& operator=(size_t indent) noexcept { indent_ = indent; return *this; } - constexpr Tab& operator=(std::string_view tab) noexcept { tab_ = tab; return *this; } ///@} // clang-format on @@ -93,7 +98,7 @@ class Tab { /// std::format("{}", greet); /// ``` template - requires std::invocable +requires std::invocable class StreamFn { public: constexpr StreamFn(F f) @@ -110,12 +115,20 @@ class StreamFn { F f_; }; -template StreamFn(F) -> StreamFn; +template +StreamFn(F) -> StreamFn; + +template +concept Formattable + = requires(std::basic_format_context>, CharT>& ctx, T const& v) { + std::formatter, CharT>{}.format(v, ctx); + }; /// Join elements of @p range with @p sep. /// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::join(v, ", "))`. /// The @p range must outlive the returned object. template +requires Formattable>> class Join { public: Join(const R& range, std::string_view sep) @@ -126,9 +139,9 @@ class Join { std::string_view sep() const { return sep_; } friend std::ostream& operator<<(std::ostream& os, const Join& j) { - for (std::string_view curr_sep; const auto& elem : j.range_) { - os << curr_sep << elem; - curr_sep = j.sep_; + for (std::string_view sep = {}; const auto& elem : j.range_) { + os << sep << elem; + sep = j.sep_; } return os; } @@ -138,7 +151,8 @@ class Join { std::string_view sep_; }; -template Join(const R&, std::string_view) -> Join; +template +Join(const R&, std::string_view) -> Join; template Join join(const R& range, std::string_view sep) { @@ -153,21 +167,32 @@ struct std::formatter> : fe::ostream_formatter {}; template struct std::formatter> { - constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } + using elem_t = std::remove_cvref_t>; + std::formatter elem_fmt; + + constexpr auto parse(std::format_parse_context& ctx) { return elem_fmt.parse(ctx); } template auto format(const fe::Join& j, FormatContext& ctx) const { - auto out = ctx.out(); - bool first = true; - for (const auto& elem : j.range()) { - if (!first) out = std::format_to(out, "{}", j.sep()); - out = std::format_to(out, "{}", elem); - first = false; + auto out = ctx.out(); + for (std::string_view sep = {}; auto const& elem : j.range()) { + out = std::ranges::copy(sep, out).out; + out = elem_fmt.format(elem, ctx); + sep = j.sep(); } return out; } }; +// clang-format off +template<> struct std::formatter : fe::ostream_formatter {}; +template<> struct std::formatter : fe::ostream_formatter {}; +template<> struct std::formatter : fe::ostream_formatter {}; +template<> struct std::formatter : fe::ostream_formatter {}; +template<> struct std::formatter : fe::ostream_formatter {}; +// clang-format on +#endif + #ifdef NDEBUG # define assertf(condition, ...) \ do { \ @@ -183,12 +208,3 @@ struct std::formatter> { } \ } while (false) #endif - -// clang-format off -template<> struct std::formatter : fe::ostream_formatter {}; -template<> struct std::formatter : fe::ostream_formatter {}; -template<> struct std::formatter : fe::ostream_formatter {}; -template<> struct std::formatter : fe::ostream_formatter {}; -template<> struct std::formatter : fe::ostream_formatter {}; -// clang-format on -#endif From a247e97952283ad6be16069ec3761249e0a228c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 12:51:16 +0200 Subject: [PATCH 08/21] added format test --- include/fe/format.h | 2 +- tests/test.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/fe/format.h b/include/fe/format.h index 7dd98d2..7b46f3d 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -131,7 +131,7 @@ template requires Formattable>> class Join { public: - Join(const R& range, std::string_view sep) + Join(const R& range, std::string_view sep = {", "}) : range_(range) , sep_(sep) {} diff --git a/tests/test.cpp b/tests/test.cpp index 06172ab..635d52e 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -246,3 +246,14 @@ TEST_CASE("term") { CHECK(oss.str() == "x"); } } + +TEST_CASE("format") { + std::vector v0; + std::vector v1 = {23}; + std::vector v2 = {23, 42}; + std::vector v3 = {23, 42, 17}; + CHECK(std::format("{}", fe::Join(v0)) == ""); + CHECK(std::format("{}", fe::Join(v1)) == "23"); + CHECK(std::format("{}", fe::Join(v2)) == "23, 42"); + CHECK(std::format("{}", fe::Join(v3)) == "23, 42, 17"); +} From 5060e6b3984c927ab3cffd2de3a51bbda9a74cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 12:54:37 +0200 Subject: [PATCH 09/21] remove fe::join. We have deduction guides for fe::Join --- include/fe/format.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index 7b46f3d..d06cab5 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -154,11 +154,6 @@ class Join { template Join(const R&, std::string_view) -> Join; -template -Join join(const R& range, std::string_view sep) { - return Join(range, sep); -} - } // namespace fe #ifndef DOXYGEN From bf62b64b2d603fd73e8a8077b514f4bd3eec7aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 20:27:26 +0200 Subject: [PATCH 10/21] fix dangling ref bug in fe::Formattable --- include/fe/format.h | 68 +++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index d06cab5..1506dd0 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -14,6 +14,29 @@ namespace fe { +/// Wrap a callable `f(std::ostream&) -> std::ostream&` so it streams via `operator<<` and `std::format`. +/// Useful for inline ad-hoc formatting: +/// ``` +/// auto greet = fe::StreamFn{[](std::ostream& os) { os << "hi"; }}; +/// std::cout << greet; +/// std::format("{}", greet); +/// ``` +template +requires std::invocable +class StreamFn { +public: + constexpr StreamFn(F f) + : f_(std::move(f)) {} + + friend std::ostream& operator<<(std::ostream& os, const StreamFn& s) { return std::invoke(s.f_, os), os; } + +private: + F f_; +}; + +template +StreamFn(F) -> StreamFn; + /// Make types that support ostream operators available for `std::format`. /// Use like this: /// ``` @@ -90,34 +113,6 @@ class Tab { size_t indent_ = 0; }; -/// Wrap a callable `f(std::ostream&) -> std::ostream&` so it streams via `operator<<` and `std::format`. -/// Useful for inline ad-hoc formatting: -/// ``` -/// auto greet = fe::StreamFn{[](std::ostream& os) -> std::ostream& { return os << "hi"; }}; -/// std::cout << greet; -/// std::format("{}", greet); -/// ``` -template -requires std::invocable -class StreamFn { -public: - constexpr StreamFn(F f) - : f_(std::move(f)) {} - - friend std::ostream& operator<<(std::ostream& os, const StreamFn& s) { - if constexpr (std::is_void_v>) - return std::invoke(s.f_, os), os; - else - return std::invoke(s.f_, os); - } - -private: - F f_; -}; - -template -StreamFn(F) -> StreamFn; - template concept Formattable = requires(std::basic_format_context>, CharT>& ctx, T const& v) { @@ -126,20 +121,21 @@ concept Formattable /// Join elements of @p range with @p sep. /// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::join(v, ", "))`. -/// The @p range must outlive the returned object. template requires Formattable>> class Join { public: - Join(const R& range, std::string_view sep = {", "}) - : range_(range) + using View = std::views::all_t; + + Join(R&& range, std::string_view sep = ", ") + : range_(std::views::all(std::forward(range))) , sep_(sep) {} - const R& range() const { return range_; } + const auto& range() const { return range_; } std::string_view sep() const { return sep_; } friend std::ostream& operator<<(std::ostream& os, const Join& j) { - for (std::string_view sep = {}; const auto& elem : j.range_) { + for (std::string_view sep{}; const auto& elem : j.range_) { os << sep << elem; sep = j.sep_; } @@ -147,12 +143,12 @@ class Join { } private: - const R& range_; + View range_; std::string_view sep_; }; template -Join(const R&, std::string_view) -> Join; +Join(R&&, std::string_view = ", ") -> Join; } // namespace fe @@ -170,7 +166,7 @@ struct std::formatter> { template auto format(const fe::Join& j, FormatContext& ctx) const { auto out = ctx.out(); - for (std::string_view sep = {}; auto const& elem : j.range()) { + for (std::string_view sep = {}; const auto& elem : j.range()) { out = std::ranges::copy(sep, out).out; out = elem_fmt.format(elem, ctx); sep = j.sep(); From ffc9c32359aeb181349222f6f5d005c3be250082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 20:32:56 +0200 Subject: [PATCH 11/21] polish format.h --- include/fe/format.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index 1506dd0..c98f191 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -28,7 +28,12 @@ class StreamFn { constexpr StreamFn(F f) : f_(std::move(f)) {} - friend std::ostream& operator<<(std::ostream& os, const StreamFn& s) { return std::invoke(s.f_, os), os; } + friend std::ostream& operator<<(std::ostream& os, StreamFn const& s) { + if constexpr (std::same_as, std::ostream&>) + return std::invoke(s.f_, os); + else + return std::invoke(s.f_, os), os; + } private: F f_; @@ -49,7 +54,7 @@ struct basic_ostream_formatter : std::formatter, Ch auto format(T const& value, FormatContext& ctx) const { std::basic_stringstream ss; ss << value; - return std::formatter, Char>::format(ss.str(), ctx); + return std::formatter, Char>::format(ss.view(), ctx); } }; @@ -122,7 +127,7 @@ concept Formattable /// Join elements of @p range with @p sep. /// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::join(v, ", "))`. template -requires Formattable>> +requires Formattable>>> class Join { public: using View = std::views::all_t; From 0cfe8b3cfddb36d1e8172e8e8e6b84517d4272e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 22:10:17 +0200 Subject: [PATCH 12/21] removing dubious helpers in fe::Tab --- include/fe/format.h | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index c98f191..9440cfd 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -66,7 +66,7 @@ class Tab { /// @name Construction ///@{ Tab(const Tab&) = default; - Tab(std::string_view tab = {"\t"}, size_t indent = 0) + Tab(std::string_view tab = {"\t"}, int indent = 0) : tab_(tab) , indent_(indent) {} @@ -75,47 +75,35 @@ class Tab { /// @name Getters ///@{ - constexpr size_t indent() const noexcept { return indent_; } + constexpr int indent() const noexcept { return indent_; } constexpr std::string_view tab() const noexcept { return tab_; } ///@} // clang-format off /// @name Creates a new Tab ///@{ - [[nodiscard]] Tab operator++(int) noexcept { return {tab_, indent_++}; } - [[nodiscard]] Tab operator--(int) noexcept { assert(indent_ > 0); return {tab_, indent_--}; } - [[nodiscard]] Tab operator+(size_t indent) const noexcept { return {tab_, indent_ + indent}; } - [[nodiscard]] Tab operator-(size_t indent) const noexcept { assert(indent_ > 0); return {tab_, indent_ - indent}; } + [[nodiscard]] Tab operator+(int indent) const noexcept { return {tab_, indent_ + indent}; } + [[nodiscard]] Tab operator-(int indent) const noexcept { assert(indent_ > 0); return {tab_, indent_ - indent}; } ///@} /// @name Modifies this Tab ///@{ constexpr Tab& operator++() noexcept { ++indent_; return *this; } constexpr Tab& operator--() noexcept { assert(indent_ > 0); --indent_; return *this; } - constexpr Tab& operator+=(size_t indent) noexcept { indent_ += indent; return *this; } - constexpr Tab& operator-=(size_t indent) noexcept { assert(indent_ > 0); indent_ -= indent; return *this; } + constexpr Tab& operator+=(int indent) noexcept { indent_ += indent; return *this; } + constexpr Tab& operator-=(int indent) noexcept { assert(indent_ > 0); indent_ -= indent; return *this; } ///@} // clang-format on friend std::ostream& operator<<(std::ostream& os, Tab tab) { - for (size_t i = 0; i != tab.indent_; ++i) + for (int i = 0; i != tab.indent_; ++i) os << tab.tab_; return os; } - /// @name Formatted Output - /// Wrap `std::format` to prefix the formatted string with the current indentation. - ///@{ - // clang-format off - template std::ostream& print (std::ostream& os, std::format_string fmt, Args&&... args) const { return os << *this << std::format(fmt, std::forward(args)...); } - template std::ostream& println(std::ostream& os, std::format_string fmt, Args&&... args) const { return (os << *this << std::format(fmt, std::forward(args)...)) << std::endl; } - template std::ostream& lnprint(std::ostream& os, std::format_string fmt, Args&&... args) const { return os << std::endl << *this << std::format(fmt, std::forward(args)...); } - // clang-format on - ///@} - private: std::string_view tab_; - size_t indent_ = 0; + int indent_ = 0; }; template From f5c96dd914051b6610fecfb384dbb6dfd876b11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sat, 9 May 2026 23:01:41 +0200 Subject: [PATCH 13/21] format.h: polish --- include/fe/format.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index 9440cfd..5eaab7a 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "fe/loc.h" @@ -68,7 +69,9 @@ class Tab { Tab(const Tab&) = default; Tab(std::string_view tab = {"\t"}, int indent = 0) : tab_(tab) - , indent_(indent) {} + , indent_(indent) { + assert(indent >= 0); + } static Tab spaces() { return Tab(std::string_view(" ")); } ///@} @@ -82,16 +85,17 @@ class Tab { // clang-format off /// @name Creates a new Tab ///@{ - [[nodiscard]] Tab operator+(int indent) const noexcept { return {tab_, indent_ + indent}; } - [[nodiscard]] Tab operator-(int indent) const noexcept { assert(indent_ > 0); return {tab_, indent_ - indent}; } + /// + [[nodiscard]] Tab operator+(int indent) const noexcept { assert(indent >= 0); return {tab_, indent_ + indent}; } + [[nodiscard]] Tab operator-(int indent) const noexcept { assert(indent >= 0 && indent_ >= indent); return {tab_, indent_ - indent}; } ///@} /// @name Modifies this Tab ///@{ constexpr Tab& operator++() noexcept { ++indent_; return *this; } constexpr Tab& operator--() noexcept { assert(indent_ > 0); --indent_; return *this; } - constexpr Tab& operator+=(int indent) noexcept { indent_ += indent; return *this; } - constexpr Tab& operator-=(int indent) noexcept { assert(indent_ > 0); indent_ -= indent; return *this; } + constexpr Tab& operator+=(int indent) noexcept { assert(indent >= 0); indent_ += indent; return *this; } + constexpr Tab& operator-=(int indent) noexcept { assert(indent >= 0 && indent_ >= indent); indent_ -= indent; return *this; } ///@} // clang-format on @@ -151,7 +155,7 @@ struct std::formatter> : fe::ostream_formatter {}; template struct std::formatter> { - using elem_t = std::remove_cvref_t>; + using elem_t = std::remove_cvref_t>>; std::formatter elem_fmt; constexpr auto parse(std::format_parse_context& ctx) { return elem_fmt.parse(ctx); } From e29ba0dc5835505a266bcb67fce72476cc2a4acc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 21:50:54 +0000 Subject: [PATCH 14/21] fix format.h review thread issues Agent-Logs-Url: https://github.com/leissa/fe/sessions/e4abd5b4-0cc5-4f01-8ef0-4b1a7f979156 Co-authored-by: leissa <8067180+leissa@users.noreply.github.com> --- include/fe/format.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index 5eaab7a..4f2476f 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -117,7 +118,7 @@ concept Formattable }; /// Join elements of @p range with @p sep. -/// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::join(v, ", "))`. +/// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::Join(v, ", "))`. template requires Formattable>>> class Join { @@ -165,7 +166,9 @@ struct std::formatter> { auto out = ctx.out(); for (std::string_view sep = {}; const auto& elem : j.range()) { out = std::ranges::copy(sep, out).out; + ctx.advance_to(out); out = elem_fmt.format(elem, ctx); + ctx.advance_to(out); sep = j.sep(); } return out; @@ -190,8 +193,8 @@ template<> struct std::formatter : fe::ostream_formatter {}; # define assertf(condition, ...) \ do { \ if (!(condition)) { \ - std::println(std::cerr, "{}:{}: assertion: ", __FILE__, __LINE__); \ - std::println(std::cerr, __VA_ARGS__); \ + std::cerr << std::format("{}:{}: assertion:\n", __FILE__, __LINE__); \ + std::cerr << std::format(__VA_ARGS__) << '\n'; \ fe::breakpoint(); \ } \ } while (false) From 96d0cd6609a3313c8a54e8a179baced406c0a72f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 21:54:19 +0000 Subject: [PATCH 15/21] style: normalize assertf newline formatting Agent-Logs-Url: https://github.com/leissa/fe/sessions/e4abd5b4-0cc5-4f01-8ef0-4b1a7f979156 Co-authored-by: leissa <8067180+leissa@users.noreply.github.com> --- include/fe/format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fe/format.h b/include/fe/format.h index 4f2476f..d29e987 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -193,7 +193,7 @@ template<> struct std::formatter : fe::ostream_formatter {}; # define assertf(condition, ...) \ do { \ if (!(condition)) { \ - std::cerr << std::format("{}:{}: assertion:\n", __FILE__, __LINE__); \ + std::cerr << std::format("{}:{}: assertion:", __FILE__, __LINE__) << '\n'; \ std::cerr << std::format(__VA_ARGS__) << '\n'; \ fe::breakpoint(); \ } \ From 4a42c3930205dd9ca97f1764efdc16bfdf06e4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sun, 10 May 2026 00:23:58 +0200 Subject: [PATCH 16/21] polish assertf macro --- include/fe/format.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/include/fe/format.h b/include/fe/format.h index d29e987..f65e3fe 100644 --- a/include/fe/format.h +++ b/include/fe/format.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -185,17 +186,18 @@ template<> struct std::formatter : fe::ostream_formatter {}; #endif #ifdef NDEBUG -# define assertf(condition, ...) \ - do { \ - (void)sizeof(condition); \ +# define assertf(condition, ...) \ + do { \ + (void)sizeof(condition); \ + __VA_OPT__((void)sizeof((__VA_ARGS__));) \ } while (false) #else -# define assertf(condition, ...) \ - do { \ - if (!(condition)) { \ - std::cerr << std::format("{}:{}: assertion:", __FILE__, __LINE__) << '\n'; \ - std::cerr << std::format(__VA_ARGS__) << '\n'; \ - fe::breakpoint(); \ - } \ +# define assertf(condition, ...) \ + do { \ + if (!(condition)) { \ + std::print(std::cerr, "{}:{}: assertion `{}` failed", __FILE__, __LINE__, #condition); \ + std::println(std::cerr __VA_OPT__(, ": " __VA_ARGS__)); \ + fe::breakpoint(); \ + } \ } while (false) #endif From 1121323c58e66f5258180cfed4055de0cbc0ae2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sun, 10 May 2026 00:36:45 +0200 Subject: [PATCH 17/21] simplify fe/enum.h --- include/fe/enum.h | 57 +++++++++++++---------------------------------- tests/test.cpp | 8 +++---- 2 files changed, 20 insertions(+), 45 deletions(-) diff --git a/include/fe/enum.h b/include/fe/enum.h index 47dd414..e76f179 100644 --- a/include/fe/enum.h +++ b/include/fe/enum.h @@ -1,62 +1,37 @@ #pragma once -#include - #include namespace fe { -/// @name is_enum -///@{ -template -struct is_bit_enum : std::false_type {}; -template -inline constexpr bool is_bit_enum_v = is_bit_enum::value; -template -concept BitEnum = std::is_enum_v && is_bit_enum_v; -///@} - /// @name Bit operations for enum classs /// Provides all kind of bit and comparison operators for an `enum class` @p E. -/// Note that the bit operators return @p E's underlying type and not the original `enum` @p E. -/// This is because the result may not be a valid `enum` value. -/// For the same reason, it doesn't make sense to declare operators such as `&=`. /// Use like this: /// ``` -/// using fe::operator&; -/// using fe::operator|; -/// using fe::operator^; -/// using fe::operator<=>; -/// using fe::operator==; -/// using fe::operator!=; -/// /// enum class MyEnum : unsigned { /// A = 1 << 0, /// B = 1 << 1, /// C = 1 << 2, /// }; /// -/// template<> struct fe::is_bit_enum : std::true_type { }; +/// template<> struct fe::is_bit_enum : std::true_type {}; /// ``` -///@{ +template +struct is_bit_enum : std::false_type {}; + +template +concept BitEnum = std::is_enum_v && is_bit_enum::value; + // clang-format off -template constexpr auto operator&(E x, E y) { return std::underlying_type_t(x) & std::underlying_type_t(y); } -template constexpr auto operator&(std::underlying_type_t x, E y) { return x & std::underlying_type_t(y); } -template constexpr auto operator&( E x, std::underlying_type_t y) { return std::underlying_type_t(x) & y ; } -template constexpr auto operator|( E x, E y) { return std::underlying_type_t(x) | std::underlying_type_t(y); } -template constexpr auto operator|(std::underlying_type_t x, E y) { return x | std::underlying_type_t(y); } -template constexpr auto operator|( E x, std::underlying_type_t y) { return std::underlying_type_t(x) | y ; } -template constexpr auto operator^( E x, E y) { return std::underlying_type_t(x) ^ std::underlying_type_t(y); } -template constexpr auto operator^(std::underlying_type_t x, E y) { return x ^ std::underlying_type_t(y); } -template constexpr auto operator^( E x, std::underlying_type_t y) { return std::underlying_type_t(x) ^ y ; } -template constexpr std::strong_ordering operator<=>(std::underlying_type_t x, E y) { return x <=> std::underlying_type_t(y); } -template constexpr std::strong_ordering operator<=>(E x, std::underlying_type_t y) { return std::underlying_type_t(x) <=> y; } -template constexpr bool operator==(std::underlying_type_t x, E y) { return x == std::underlying_type_t(y); } -template constexpr bool operator!=(std::underlying_type_t x, E y) { return x != std::underlying_type_t(y); } -template constexpr bool operator==(E x, std::underlying_type_t y) { return std::underlying_type_t(x) == y; } -template constexpr bool operator!=(E x, std::underlying_type_t y) { return std::underlying_type_t(x) != y; } +template constexpr auto to_underlying(E e) noexcept { return static_cast>(e); } +template constexpr E operator|(E a, E b) noexcept { return static_cast(to_underlying(a) | to_underlying(b)); } +template constexpr E operator&(E a, E b) noexcept { return static_cast(to_underlying(a) & to_underlying(b)); } +template constexpr E operator^(E a, E b) noexcept { return static_cast(to_underlying(a) ^ to_underlying(b)); } +template constexpr E operator~(E a) noexcept { return static_cast(~to_underlying(a)); } +template constexpr E& operator|=(E& a, E b) noexcept { return a = (a | b); } +template constexpr E& operator&=(E& a, E b) noexcept { return a = (a & b); } +template constexpr E& operator^=(E& a, E b) noexcept { return a = (a ^ b); } +template constexpr bool has_flag(E value, E flag) noexcept { return (value & flag) == flag; } // clang-format on -///@} - } // namespace fe diff --git a/tests/test.cpp b/tests/test.cpp index 635d52e..fd36af4 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -213,10 +213,10 @@ using fe::operator|; using fe::operator^; TEST_CASE("enum") { - static_assert((MyEnum::A & MyEnum::A) == 1); - static_assert((MyEnum::A & MyEnum::B) == 0); - static_assert((MyEnum::A | MyEnum::B) == 3); - static_assert((MyEnum::A ^ MyEnum::A) == 0); + static_assert(fe::to_underlying(MyEnum::A & MyEnum::A) == 1); + static_assert(fe::to_underlying(MyEnum::A & MyEnum::B) == 0); + static_assert(fe::to_underlying(MyEnum::A | MyEnum::B) == 3); + static_assert(fe::to_underlying(MyEnum::A ^ MyEnum::A) == 0); } TEST_CASE("term") { From 8fc13e470333c7a2498627da58e50272b4eadc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sun, 10 May 2026 00:38:39 +0200 Subject: [PATCH 18/21] linux/doxygen: use gcc/g++ 14 --- .github/workflows/doxygen.yml | 4 ++-- .github/workflows/linux.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index 5f2ab55..aca62bf 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -25,7 +25,7 @@ jobs: with: submodules: recursive - - name: Install Graphviz + - name: Install Graphviz, gcc-14, g++-14 run: | sudo apt-get update sudo apt-get install -y --no-install-recommends graphviz @@ -49,7 +49,7 @@ jobs: tar -C "${{ github.workspace }}/.cache" -xf "${RUNNER_TEMP}/${archive}" - name: Configure - run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF -DFE_BUILD_DOCS=ON -DDOXYGEN_EXECUTABLE=${{ env.DOXYGEN_CACHE_DIR }}/bin/doxygen + run: CC=gcc-14 CXX=g++-14 cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF -DFE_BUILD_DOCS=ON -DDOXYGEN_EXECUTABLE=${{ env.DOXYGEN_CACHE_DIR }}/bin/doxygen - name: Build run: cmake --build ${{github.workspace}}/build -v --target docs diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a8396d4..7a9b43d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,13 +19,13 @@ jobs: with: submodules: recursive - - name: Install Valgrind + - name: Install Valgrind, gcc-14, g++-14 run: | sudo apt-get update - sudo apt-get install -y valgrind + sudo apt-get install -y valgrind gcc-14, g++-14 - name: Configure - run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DBUILD_TESTING=ON + run: CC=gcc-14 CXX=g++-14 cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DBUILD_TESTING=ON - name: Build run: cmake --build ${{github.workspace}}/build -v From 70fa96b21ce21c90513c4ff33e13bf72291a3830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sun, 10 May 2026 00:40:32 +0200 Subject: [PATCH 19/21] fix install command --- .github/workflows/doxygen.yml | 2 +- .github/workflows/linux.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index aca62bf..3cad064 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -28,7 +28,7 @@ jobs: - name: Install Graphviz, gcc-14, g++-14 run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends graphviz + sudo apt-get install -y --no-install-recommends graphviz gcc-14 g++-14 - name: Restore Doxygen cache id: cache-doxygen diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7a9b43d..74a3027 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -22,7 +22,7 @@ jobs: - name: Install Valgrind, gcc-14, g++-14 run: | sudo apt-get update - sudo apt-get install -y valgrind gcc-14, g++-14 + sudo apt-get install -y valgrind gcc-14 g++-14 - name: Configure run: CC=gcc-14 CXX=g++-14 cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DBUILD_TESTING=ON From 32523d2fde779e772bae7b5e3d7771af2c6fac81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sun, 10 May 2026 00:55:33 +0200 Subject: [PATCH 20/21] put BitEnum operators into global scope --- include/fe/enum.h | 23 ++++++++++++----------- tests/test.cpp | 3 --- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/include/fe/enum.h b/include/fe/enum.h index e76f179..cbba46b 100644 --- a/include/fe/enum.h +++ b/include/fe/enum.h @@ -22,16 +22,17 @@ struct is_bit_enum : std::false_type {}; template concept BitEnum = std::is_enum_v && is_bit_enum::value; -// clang-format off -template constexpr auto to_underlying(E e) noexcept { return static_cast>(e); } -template constexpr E operator|(E a, E b) noexcept { return static_cast(to_underlying(a) | to_underlying(b)); } -template constexpr E operator&(E a, E b) noexcept { return static_cast(to_underlying(a) & to_underlying(b)); } -template constexpr E operator^(E a, E b) noexcept { return static_cast(to_underlying(a) ^ to_underlying(b)); } -template constexpr E operator~(E a) noexcept { return static_cast(~to_underlying(a)); } -template constexpr E& operator|=(E& a, E b) noexcept { return a = (a | b); } -template constexpr E& operator&=(E& a, E b) noexcept { return a = (a & b); } -template constexpr E& operator^=(E& a, E b) noexcept { return a = (a ^ b); } -template constexpr bool has_flag(E value, E flag) noexcept { return (value & flag) == flag; } -// clang-format on +template constexpr auto to_underlying(E e) noexcept { return static_cast>(e); } +template constexpr bool has_flag(E value, E flag) noexcept { return (value & flag) == flag; } } // namespace fe + +// clang-format off +template constexpr E operator|(E a, E b) noexcept { return static_cast(fe::to_underlying(a) | fe::to_underlying(b)); } +template constexpr E operator&(E a, E b) noexcept { return static_cast(fe::to_underlying(a) & fe::to_underlying(b)); } +template constexpr E operator^(E a, E b) noexcept { return static_cast(fe::to_underlying(a) ^ fe::to_underlying(b)); } +template constexpr E operator~(E a) noexcept { return static_cast(~fe::to_underlying(a)); } +template constexpr E& operator|=(E& a, E b) noexcept { return a = (a | b); } +template constexpr E& operator&=(E& a, E b) noexcept { return a = (a & b); } +template constexpr E& operator^=(E& a, E b) noexcept { return a = (a ^ b); } +// clang-format on diff --git a/tests/test.cpp b/tests/test.cpp index fd36af4..3bbd115 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -208,9 +208,6 @@ enum class MyEnum : unsigned { template<> struct fe::is_bit_enum : std::true_type {}; -using fe::operator&; -using fe::operator|; -using fe::operator^; TEST_CASE("enum") { static_assert(fe::to_underlying(MyEnum::A & MyEnum::A) == 1); From 5c36ffdf193a1d30688013ad2f99c1894b57c984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Lei=C3=9Fa?= Date: Sun, 10 May 2026 00:57:31 +0200 Subject: [PATCH 21/21] put enum stuff into correct order --- include/fe/enum.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/fe/enum.h b/include/fe/enum.h index cbba46b..8ff899a 100644 --- a/include/fe/enum.h +++ b/include/fe/enum.h @@ -23,7 +23,6 @@ template concept BitEnum = std::is_enum_v && is_bit_enum::value; template constexpr auto to_underlying(E e) noexcept { return static_cast>(e); } -template constexpr bool has_flag(E value, E flag) noexcept { return (value & flag) == flag; } } // namespace fe @@ -35,4 +34,9 @@ template constexpr E operator~(E a) noexcept { return static_cast template constexpr E& operator|=(E& a, E b) noexcept { return a = (a | b); } template constexpr E& operator&=(E& a, E b) noexcept { return a = (a & b); } template constexpr E& operator^=(E& a, E b) noexcept { return a = (a ^ b); } + +namespace fe { +template constexpr bool has_flag(E value, E flag) noexcept { return (value & flag) == flag; } +} // namespace fe + // clang-format on