From 8dc0301a6747945c263c5c5cfa678eee9f4c5edc Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 8 Apr 2026 17:58:12 +0200 Subject: [PATCH 1/7] Document etl::format_to and etl::print (#1378) * Print test names at test time (#1343) * Document etl::format_to and etl::print --------- Co-authored-by: John Wellbelove --- docs/format.md | 382 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 docs/format.md diff --git a/docs/format.md b/docs/format.md new file mode 100644 index 00000000..1f3a5e37 --- /dev/null +++ b/docs/format.md @@ -0,0 +1,382 @@ +# ETL Format & Print + +## 1. Overview + +ETL provides text formatting facilities modelled on C++20 `std::format` and C++23 +`std::print`. They allow type-safe, positional formatting of values into strings +or directly to a character output device — without heap allocation. + +**Minimum language standard:** C++11 (`ETL_USING_CPP11`). + +**Headers:** + +| Header | Provides | +|---|---| +| `etl/format.h` | `etl::format_to`, `etl::format_to_n`, `etl::formatted_size` | +| `etl/print.h` | `etl::print`, `etl::println` (includes `etl/format.h`) | + +## 2. `etl::format_to` + +### Generic output-iterator overload + +```cpp +template +OutputIt format_to(OutputIt out, format_string fmt, Args&&... args); +``` + +Formats `args` according to the format string `fmt` and writes the result through +the output iterator `out`. Returns an iterator past the last character written. + +`OutputIt` can be any output iterator whose dereferenced type is assignable from +`char`, for example `etl::istring::iterator` or +`etl::back_insert_iterator`. + +```cpp +etl::string<100> s; + +// Using a raw iterator — you must resize the string yourself +etl::istring::iterator result = etl::format_to(s.begin(), "{0} {1}", 34, 56); +s.uninitialized_resize(static_cast(result - s.begin())); +// s == "34 56" + +// Using a back_insert_iterator — string grows automatically +s.clear(); +etl::back_insert_iterator it(s); +etl::format_to(it, "{} {}", 65, 34); +// s == "65 34" +``` + +### `etl::istring&` overload (ETL-specific) + +```cpp +template +etl::istring::iterator format_to(etl::istring& out, + format_string fmt, + Args&&... args); +``` + +Convenience overload that writes into an `etl::istring` (or any derived +`etl::string`). The string is automatically resized to the number of +characters written, up to `out.max_size()`. Returns an iterator past the +last character written. + +```cpp +etl::string<100> s; +etl::format_to(s, "Hello, {}!", "world"); +// s == "Hello, world!" +``` + +### `etl::format_to_n` + +```cpp +template +OutputIt format_to_n(OutputIt out, size_t n, + format_string fmt, Args&&... args); +``` + +Like `format_to`, but writes **at most** `n` characters. Characters beyond the +limit are silently discarded. + +```cpp +etl::string<10> s = "abcdefghij"; +etl::format_to_n(s.begin(), 3, "xy{}", 123); +// s == "xy1defghij" (only 3 chars written) +``` + +## 3. `etl::formatted_size` + +```cpp +template +size_t formatted_size(format_string fmt, Args&&... args); +``` + +Returns the total number of characters that `format_to` would produce, without +actually writing anything. Useful for pre-computing buffer sizes. + +```cpp +size_t n; +n = etl::formatted_size(""); // 0 +n = etl::formatted_size("{}", ""); // 0 +n = etl::formatted_size("xyz{}", 12); // 5 +n = etl::formatted_size("{}", "abc"); // 3 +``` + +## 4. `etl::print` and `etl::println` + +Declared in `etl/print.h`. + +### `etl::print` + +```cpp +template +void print(etl::format_string fmt, Args&&... args); +``` + +Formats the arguments and outputs each character by calling `etl_putchar()`. + +### `etl::println` + +```cpp +// With arguments — prints formatted text followed by '\n' +template +void println(etl::format_string fmt, Args&&... args); + +// Without arguments — prints a bare newline +void println(); +``` + +### Implementing `etl_putchar` + +`etl/print.h` declares (but does not define) the following C-linkage function: + +```cpp +extern "C" void etl_putchar(int c); +``` + +You **must** provide a definition in your project. The `int` parameter follows +the convention of the standard `putchar()` and carries a single `char` value. + +Typical implementations forward to a UART, a debug probe, `putchar`, or any +other single-character output sink: + +```cpp +// Example: forward to standard putchar +extern "C" void etl_putchar(int c) +{ + putchar(c); +} +``` + +### Example + +```cpp +etl::print("x = {}, y = {}\n", 10, 20); // "x = 10, y = 20\n" +etl::println("Hello, {}!", "world"); // "Hello, world!\n" +etl::println(); // "\n" +``` + +## 5. Format String Syntax + +A format string is ordinary text with **replacement fields** delimited by braces: + +``` +"literal text {} more text {1:>10} end" +``` + +### Replacement field grammar + +``` +replacement_field ::= '{' [arg_id] [':' format_spec] '}' +arg_id ::= integer // e.g. 0, 1, 2 … +format_spec ::= [[fill]align] [sign] ['#'] ['0'] [width] ['.' precision] ['L'] [type] +``` + +| Component | Syntax | Description | +|---|---|---| +| **Argument index** | `{0}`, `{1}`, … | Manual positional indexing. Cannot be mixed with automatic indexing. | +| **Automatic index** | `{}` | Uses the next argument in order. Cannot be mixed with manual indexing. | +| **Fill character** | any character except `{` or `}` | Used together with an alignment specifier. Default is space (` `). | +| **Alignment** | `<` left, `>` right, `^` center | Aligns the formatted value within the given *width*. | +| **Sign** | `+` always, `-` negative only (default), ` ` space for positive | Controls sign display for numeric types. | +| **`#` (alt form)** | `#` | Adds `0x`/`0X` for hex, `0b`/`0B` for binary, `0` for octal. | +| **`0` (zero-pad)** | `0` | Pads the number with leading zeros (after sign/prefix). | +| **Width** | integer, or `{}` / `{n}` | Minimum field width. Supports nested replacement fields for dynamic width. | +| **Precision** | `.` integer, or `.{}` / `.{n}` | For strings: maximum characters to output. For floats: number of decimal digits. Supports nested replacement fields. | +| **`L`** | `L` | Locale-specific flag (parsed but currently ignored). | +| **Type** | see [Presentation Types](#7-presentation-types-per-argument-kind) | Selects the output representation. | + +### Examples + +```cpp +etl::format_to(s, "{:>10}", 42); // " 42" +etl::format_to(s, "{:*^10}", 42); // "****42****" +etl::format_to(s, "{:+05d}", 67); // "+00067" +etl::format_to(s, "{:#x}", 0x3f4); // "0x3f4" +etl::format_to(s, "{:.3s}", "abcdef"); // "abc" +etl::format_to(s, "{1} {0}", 1, 2); // "2 1" +``` + +## 6. Supported Argument Types + +The core set of formattable types (matching `std::basic_format_arg`): + +| Category | Types | +|---|---| +| Boolean | `bool` | +| Character | `char` | +| Signed integer | `int`, `long long int` | +| Unsigned integer | `unsigned int`, `unsigned long long int` | +| Floating-point *(opt-in)* | `float`, `double`, `long double` — requires `ETL_USING_FORMAT_FLOATING_POINT` | +| String | `const char*`, `etl::string_view` | +| Pointer | `const void*` | + +### Implicit conversions + +Types not listed above are converted automatically before formatting: + +| Source type | Stored as | +|---|---| +| `short` | `int` | +| `unsigned short`, `uint16_t` | `unsigned int` | +| `long int` | `int` or `long long int` (platform-dependent) | +| `unsigned long int`, `size_t` | `unsigned int` or `unsigned long long int` | +| `int8_t` (`signed char`) | `char` | +| `uint8_t` (`unsigned char`) | `char` | +| `int16_t` | `int` | +| `uint32_t` | `unsigned int` | +| `int32_t` | `int` | +| `etl::string` | `etl::string_view` (lifetime of the temporary is guaranteed) | +| any pointer `T*` | `const void*` | + +## 7. Presentation Types per Argument Kind + +### Integers (`int`, `unsigned int`, `long long int`, `unsigned long long int`) + +| Type | Meaning | Example | +|---|---|---| +| `d` *(default)* | Decimal | `134` → `"134"` | +| `x` | Lowercase hexadecimal | `0x3f4` → `"3f4"` | +| `X` | Uppercase hexadecimal | `0x3f4` → `"3F4"` | +| `o` | Octal | `034` → `"34"` | +| `b` | Lowercase binary | `0b1010` → `"1010"` | +| `B` | Uppercase binary | `0b1010` → `"1010"` | +| `c` | Character (value as char) | `67` → `"C"` | + +With `#`: prefixes `0x`/`0X`, `0b`/`0B`, or leading `0` for octal. + +### Characters (`char`, `signed char`, `unsigned char`) + +| Type | Meaning | Example | +|---|---|---| +| `c` *(default)* | Character itself | `'s'` → `"s"` | +| `?` | Debug / escaped | `'\n'` → `"'\\n'"` | +| `d` | Decimal code point | `'a'` → `"97"` | +| `x` / `X` | Hex code point | `'a'` → `"61"` | + +### Booleans (`bool`) + +| Type | Meaning | Example | +|---|---|---| +| *(default)* | `false` / `true` | `true` → `"true"` | +| `s` | Same as default | `true` → `"true"` | +| `d` | `0` / `1` | `true` → `"1"` | +| `x` / `X` | Hex `0` / `1` | `true` → `"1"` | +| `o` | Octal (with `#`: `01`) | `true` → `"01"` | + +### Strings (`const char*`, `etl::string_view`, `etl::string`) + +| Type | Meaning | Example | +|---|---|---| +| `s` *(default)* | String output | `"data1"` → `"data1"` | +| `?` | Debug / escaped | `"data1\n"` → `"\"data1\\n\""` | + +Width and precision apply: width sets the minimum field width; precision (`.N`) +truncates the string to at most *N* characters. + +```cpp +etl::format_to(s, "{:>10s}", "data1"); // " data1" +etl::format_to(s, "{:.3s}", "abcdef"); // "abc" +etl::format_to(s, ".{:^8.3s}!", "data1"); // ". dat !" +``` + +### Pointers (`const void*`) + +| Type | Meaning | Example | +|---|---|---| +| `p` *(default)* | Lowercase hex with `0x` prefix | `nullptr` → `"0x0"` | +| `P` | Uppercase hex with `0X` prefix | `nullptr` → `"0X0"` | + +### Floating-point (`float`, `double`, `long double`) + +Requires `ETL_USING_FORMAT_FLOATING_POINT`. + +| Type | Meaning | Example | +|---|---|---| +| *(default)* | Shortest representation | `1.5f` → `"1.5"` | +| `e` / `E` | Scientific notation | `1.0f` → `"1.000000e+00"` | +| `f` / `F` | Fixed-point notation | `1.125f` → `"1.125000"` | +| `g` / `G` | General (fixed or scientific) | `1e10f` → `"1.000000e+10"` | +| `a` / `A` | Hexadecimal floating-point | `1.5f` → `"0x1.8p+0"` | + +`nan`, `inf` (lowercase for `e`/`f`/`g`/`a`, uppercase for `E`/`F`/`G`/`A`). + +## 8. Escape Sequences and Literal Braces + +### Literal braces + +Because `{` and `}` delimit replacement fields, they must be escaped by +doubling: + +| Input | Output | +|---|---| +| `{{` | `{` | +| `}}` | `}` | + +```cpp +etl::format_to(s, "abc{{def"); // "abc{def" +etl::format_to(s, "}}abc"); // "}abc" +``` + +### Debug / escaped presentation (`?`) + +The `?` type specifier produces a debug representation: + +- **Characters** are wrapped in single quotes with C-style escape sequences: + + | Character | Output | + |---|---| + | `\t` | `'\\t'` | + | `\n` | `'\\n'` | + | `\r` | `'\\r'` | + | `"` | `'\\\"'` | + | `'` | `'\\''` | + | `\\` | `'\\\\'` | + +- **Strings** are wrapped in double quotes with the same escape sequences: + + ```cpp + etl::format_to(s, "{:?}", "data1\n"); // "\"data1\\n\"" + ``` + +## 9. Error Handling + +Invalid format strings cause an `etl::bad_format_string_exception` (derived from +`etl::format_exception`, which is derived from `etl::exception`). + +Common error conditions: + +| Condition | Example | +|---|---| +| Missing closing brace | `"a{b"` | +| Unescaped `}` without matching `{` | `"a}b"` | +| Invalid characters inside `{}` | `"a{b}"` | +| Argument index out of range | `"{1}"` with only one argument | +| Mixing manual and automatic indexing | `"{0} {}"` | +| Invalid type specifier for the argument | `"{:d}"` on a `string_view` | +| Double colon in format spec | `"{::}"` | +| Precision on an integer | `"{:+#05.5X}"` on an `int` | + +```cpp +etl::string<100> s; +// These all throw etl::bad_format_string_exception: +etl::format_to(s, "a{b}", 1); // bad index spec +etl::format_to(s, "a{b", 1); // closing brace missing +etl::format_to(s, "a}b"); // unescaped } +etl::format_to(s, "{:d}", sv); // invalid type for string_view +``` + +> **Note:** On C++20 and later, compile-time format string validation through +> `consteval` is planned but not yet fully implemented. + +## 10. Differences from `std::format` + +| Area | `std::format` (C++20/23) | ETL | +|---|---|---| +| **Output target** | Returns `std::string` | Writes through an output iterator or into `etl::istring&` — no heap allocation. | +| **`etl::istring&` overload** | Not available | `format_to(etl::istring&, ...)` automatically resizes the string. | +| **`print` / `println` output** | Writes to `FILE*` / `stdout` | Writes character-by-character via user-defined `etl_putchar(int)`. | +| **Floating-point support** | Always available | Opt-in via `ETL_USING_FORMAT_FLOATING_POINT`. | +| **User-defined formatters** | `std::formatter` specialisations | Not yet supported. | +| **Locale** | `L` flag uses `std::locale` | `L` flag is parsed but has no effect. | +| **Compile-time validation** | Enforced via `consteval` on C++20 | Planned; currently validates at run time and throws `etl::bad_format_string_exception`. | +| **`format_to_n` return type** | `std::format_to_n_result` | Returns the underlying `OutputIt` directly. | From 7971824914eb58c21868d9cf43c8b019b1c46418 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Thu, 9 Apr 2026 07:21:19 +0100 Subject: [PATCH 2/7] Add the ability to specify the callback type to etl closure (#1393) * Print test names at test time (#1343) * Modified closure to accept the callback type as a template parameter * Modified closure to accept the callback type as a template parameter * Applied clang-format * Fixed C++03 compatibility * Fixed C++03 compatibility # Conflicts: # include/etl/closure.h --------- Co-authored-by: Roland Reichwein Co-authored-by: John Wellbelove --- include/etl/closure.h | 170 +++++++------- test/CMakeLists.txt | 7 +- ...=> test_closure_with_default_delegate.cpp} | 2 +- ...osure_with_default_delegate_constexpr.cpp} | 2 +- test/test_closure_with_inplace_function.cpp | 208 ++++++++++++++++++ test/vs2022/etl.vcxproj | 5 +- test/vs2022/etl.vcxproj.filters | 13 +- 7 files changed, 311 insertions(+), 96 deletions(-) rename test/{test_closure.cpp => test_closure_with_default_delegate.cpp} (99%) rename test/{test_closure_constexpr.cpp => test_closure_with_default_delegate_constexpr.cpp} (99%) create mode 100644 test/test_closure_with_inplace_function.cpp diff --git a/include/etl/closure.h b/include/etl/closure.h index 54b0fb36..2c150ec3 100644 --- a/include/etl/closure.h +++ b/include/etl/closure.h @@ -33,53 +33,62 @@ SOFTWARE. #include "platform.h" #include "delegate.h" +#include "invoke.h" #include "tuple.h" #include "type_list.h" #include "utility.h" namespace etl { -#if ETL_USING_CPP11 && !defined(ETL_CLOSURE_FORCE_CPP03_IMPLEMENTATION) //************************************************************************* /// Base template for closure. + /// \tparam TSignature The callback signature. + /// \tparam TCallback The callback type, defaults to etl::delegate. //************************************************************************* - template + template > class closure; +#if ETL_USING_CPP11 && !defined(ETL_CLOSURE_FORCE_CPP03_IMPLEMENTATION) //************************************************************************* - /// Closure for binding arguments to a delegate and invoking it later. - /// Stores a delegate and its arguments, allowing deferred execution. + /// Closure for binding arguments to a callback and invoking it later. + /// Stores a callback and its arguments, allowing deferred execution. /// Arguments are stored in a tuple and can be rebound using bind(). /// Example usage: /// \code /// etl::closure c(my_delegate, 1, 2); /// c(); // Invokes my_delegate(1, 2) /// \endcode - /// \tparam TReturn The return type of the delegate. - /// \tparam TArgs The argument types of the delegate. + /// \tparam TReturn The return type of the callback. + /// \tparam TArgs The argument types of the callback. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - using delegate_type = etl::delegate; ///< The delegate type to be invoked. - using argument_types = etl::type_list; ///< The type list of arguments. + ETL_DEPRECATED_REASON("Use callback_type") using delegate_type = + TCallback; ///< The callback type to be invoked. Deprecated, use callback_type instead. + using callback_type = TCallback; ///< The callback type to be invoked. + using argument_types = etl::type_list; ///< The type list of arguments. + + static_assert(etl::is_invocable_r::value, "Callback is not invocable with the specified arguments"); + static_assert(etl::is_copy_constructible::value, "Callback type must be copy constructible"); //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. - /// \param args The arguments to bind to the delegate. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. + /// \param args The arguments to bind to the callback. //********************************************************************* - ETL_CONSTEXPR14 closure(const delegate_type& f, const TArgs... args) + ETL_CONSTEXPR14 closure(const callback_type& f, const TArgs... args) : m_f(f) , m_args(args...) { } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* ETL_CONSTEXPR14 TReturn operator()() const { @@ -131,7 +140,7 @@ namespace etl //********************************************************************* /// Execute the closure with the stored arguments. /// \tparam idx Index sequence for tuple unpacking. - /// \return The result of the delegate invocation. + /// \return The result of the callback invocation. //********************************************************************* template ETL_CONSTEXPR14 TReturn execute(etl::index_sequence) const @@ -139,43 +148,38 @@ namespace etl return m_f(etl::get(m_args)...); } - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. etl::tuple m_args; ///< The bound arguments. }; #else //************************************************************************* - /// Base template for closure. - //************************************************************************* - template - class closure; - - //************************************************************************* - /// Closure for binding one argument to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding one argument to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - /// The delegate type to be invoked. - typedef etl::delegate delegate_type; + /// The callback type to be invoked. + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its argument. - /// \param f The delegate to be invoked. - /// \param arg0 The argument to bind to the delegate. + /// Construct a closure with a callback and its argument. + /// \param f The callback to be invoked. + /// \param arg0 The argument to bind to the callback. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0) + closure(const callback_type& f, const TArg0 arg0) : m_f(f) , m_arg0(arg0) { } //********************************************************************* - /// Invoke the stored delegate with the bound argument. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound argument. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -184,30 +188,31 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; }; //************************************************************************* - /// Closure for binding two arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding two arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -215,8 +220,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -225,33 +230,34 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; }; //************************************************************************* - /// Closure for binding three arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding three arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. /// \tparam TArg2 The type of the third argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. /// \param arg2 The third argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -260,8 +266,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -270,36 +276,37 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; TArg2 m_arg2; }; //************************************************************************* - /// Closure for binding four arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding four arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. /// \tparam TArg2 The type of the third argument. /// \tparam TArg3 The type of the fourth argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. /// \param arg2 The third argument to bind. /// \param arg3 The fourth argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -309,8 +316,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -319,7 +326,7 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; TArg2 m_arg2; @@ -327,31 +334,32 @@ namespace etl }; //************************************************************************* - /// Closure for binding five arguments to a delegate and invoking it later. - /// \tparam TReturn The return type of the delegate. + /// Closure for binding five arguments to a callback and invoking it later. + /// \tparam TReturn The return type of the callback. /// \tparam TArg0 The type of the first argument. /// \tparam TArg1 The type of the second argument. /// \tparam TArg2 The type of the third argument. /// \tparam TArg3 The type of the fourth argument. /// \tparam TArg4 The type of the fifth argument. + /// \tparam TCallback The callback type. //************************************************************************* - template - class closure + template + class closure { public: - typedef etl::delegate delegate_type; + typedef TCallback callback_type; //********************************************************************* - /// Construct a closure with a delegate and its arguments. - /// \param f The delegate to be invoked. + /// Construct a closure with a callback and its arguments. + /// \param f The callback to be invoked. /// \param arg0 The first argument to bind. /// \param arg1 The second argument to bind. /// \param arg2 The third argument to bind. /// \param arg3 The fourth argument to bind. /// \param arg4 The fifth argument to bind. //********************************************************************* - closure(const delegate_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3, const TArg4 arg4) + closure(const callback_type& f, const TArg0 arg0, const TArg1 arg1, const TArg2 arg2, const TArg3 arg3, const TArg4 arg4) : m_f(f) , m_arg0(arg0) , m_arg1(arg1) @@ -362,8 +370,8 @@ namespace etl } //********************************************************************* - /// Invoke the stored delegate with the bound arguments. - /// \return The result of the delegate invocation. + /// Invoke the stored callback with the bound arguments. + /// \return The result of the callback invocation. //********************************************************************* TReturn operator()() const { @@ -372,7 +380,7 @@ namespace etl private: - delegate_type m_f; ///< The delegate to invoke. + callback_type m_f; ///< The callback to invoke. TArg0 m_arg0; TArg1 m_arg1; TArg2 m_arg2; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a559add4..9f325bb9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -83,8 +83,9 @@ add_executable(etl_tests test_circular_buffer.cpp test_circular_buffer_external_buffer.cpp test_circular_iterator.cpp - test_closure.cpp - test_closure_constexpr.cpp + test_closure_with_default_delegate.cpp + test_closure_with_default_delegate_constexpr.cpp + test_closure_with_inplace_function.cpp test_compare.cpp test_concepts.cpp test_constant.cpp @@ -523,7 +524,7 @@ if ((CMAKE_CXX_COMPILER_ID MATCHES "GNU") OR (CMAKE_CXX_COMPILER_ID MATCHES "Cla target_link_options(etl_tests PRIVATE -fsanitize=address,undefined,bounds - ) + ) endif() endif () endif () diff --git a/test/test_closure.cpp b/test/test_closure_with_default_delegate.cpp similarity index 99% rename from test/test_closure.cpp rename to test/test_closure_with_default_delegate.cpp index 3e01ca8b..a7ef4f35 100644 --- a/test/test_closure.cpp +++ b/test/test_closure_with_default_delegate.cpp @@ -34,7 +34,7 @@ SOFTWARE. namespace { - SUITE(test_closure) + SUITE(test_closure_with_default_delegate) { int f1(int a1) { diff --git a/test/test_closure_constexpr.cpp b/test/test_closure_with_default_delegate_constexpr.cpp similarity index 99% rename from test/test_closure_constexpr.cpp rename to test/test_closure_with_default_delegate_constexpr.cpp index 73c4b68c..57dcc18d 100644 --- a/test/test_closure_constexpr.cpp +++ b/test/test_closure_with_default_delegate_constexpr.cpp @@ -34,7 +34,7 @@ SOFTWARE. namespace { - SUITE(test_closure) + SUITE(test_closure_with_default_delegate_constexpr) { static constexpr int f1(int a1) { diff --git a/test/test_closure_with_inplace_function.cpp b/test/test_closure_with_inplace_function.cpp new file mode 100644 index 00000000..cd67ffba --- /dev/null +++ b/test/test_closure_with_inplace_function.cpp @@ -0,0 +1,208 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 BMW AG, John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/closure.h" +#include "etl/inplace_function.h" + +#include + +namespace +{ + SUITE(test_closure_with_inplace_function) + { + int f1(int a1) + { + return a1 * 3; + } + + int f1_throwing(int) + { + throw std::runtime_error("throwing function"); + } + + void f1_void(int) {} + + int f1_ref(int& a1) + { + return a1 * 3; + } + + int f2(int a1, int a2) + { + return a1 * 3 + a2; + } + + int f3(int a1, int a2, int a3) + { + return a1 * 3 + a2 * a3; + } + + int f4(int a1, int a2, int a3, int a4) + { + return a1 * 3 + a2 * a3 + a4; + } + + int f5(int a1, int a2, int a3, int a4, int a5) + { + return a1 * 3 + a2 * a3 + a4 * a5; + } + + using f1_type = int(int); + using f1_ref_type = int(int&); + using f1_void_type = void(int); + using f2_type = int(int, int); + using f3_type = int(int, int, int); + using f4_type = int(int, int, int, int); + using f5_type = int(int, int, int, int, int); + + using ipf1_type = etl::inplace_function; + using ipf1_ref_type = etl::inplace_function; + using ipf1_void_type = etl::inplace_function; + using ipf2_type = etl::inplace_function; + using ipf3_type = etl::inplace_function; + using ipf4_type = etl::inplace_function; + using ipf5_type = etl::inplace_function; + + ipf1_type ipf1 = ipf1_type::create<&f1>(); + ipf1_type ipf1_throwing = ipf1_type::create<&f1_throwing>(); + ipf1_ref_type ipf1_ref = ipf1_ref_type::create<&f1_ref>(); + ipf1_void_type ipf1_void = ipf1_void_type::create<&f1_void>(); + ipf2_type ipf2 = ipf2_type::create<&f2>(); + ipf3_type ipf3 = ipf3_type::create<&f3>(); + ipf4_type ipf4 = ipf4_type::create<&f4>(); + ipf5_type ipf5 = ipf5_type::create<&f5>(); + + //************************************************************************* + TEST(test_1_arg) + { + etl::closure c1(ipf1, 4); + CHECK_EQUAL(12, c1()); + } + + //************************************************************************* + TEST(test_1_arg_reference) + { + int v1 = 4; + etl::closure c1_ref(ipf1_ref, v1); + CHECK_EQUAL(12, c1_ref()); + v1 = 5; + CHECK_EQUAL(15, c1_ref()); + } + + //************************************************************************* + TEST(test_1_arg_lambda) + { + auto l = [](int a) + { + return a + 11; + }; + ipf1_type ipf1_lambda(l); + + etl::closure c1_lambda(ipf1_lambda, 5); + CHECK_EQUAL(16, c1_lambda()); + } + + //************************************************************************* + TEST(test_throwing) + { + etl::closure c1(ipf1_throwing, 4); + CHECK_THROW(c1(), std::runtime_error); + } + + //************************************************************************* + TEST(test_void) + { + etl::closure c1(ipf1_void, 4); + c1(); + } + + //************************************************************************* + TEST(test_2_args) + { + etl::closure c2(ipf2, 4, 3); + CHECK_EQUAL(15, c2()); + } + +#if ETL_USING_CPP11 && !defined(ETL_CLOSURE_FORCE_CPP03_IMPLEMENTATION) + //************************************************************************* + TEST(test_2_args_bind) + { + etl::closure c2(ipf2, 4, 3); + CHECK_EQUAL(15, c2()); + + c2.bind<0>(7); + c2.bind<1>(8); + CHECK_EQUAL(29, c2()); + } + + //************************************************************************* + TEST(test_2_args_bind_all) + { + etl::closure c2(ipf2, 4, 3); + CHECK_EQUAL(15, c2()); + + c2.bind(7, 8); + CHECK_EQUAL(29, c2()); + } +#endif + + //************************************************************************* + TEST(test_3_args) + { + etl::closure c3(ipf3, 4, 3, 2); + CHECK_EQUAL(18, c3()); + } + + //************************************************************************* + TEST(test_4_args) + { + etl::closure c4(ipf4, 4, 3, 2, 1); + CHECK_EQUAL(19, c4()); + } + + //************************************************************************* + TEST(test_5_args) + { + etl::closure c5(ipf5, 4, 3, 2, 1, 5); + CHECK_EQUAL(23, c5()); + } + + //************************************************************************* + TEST(test_bind_static_assert) + { + etl::closure c(ipf2, 1, 2); + + // Uncomment to generate static_assert errors. + // c.bind(1); // Argument count mismatch + // c.bind(1, 2, 3); // Argument count mismatch + // c.bind(1, std::string()); // Argument is not convertible + } + } +} // namespace diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index 93ea62c4..0e0b6601 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -10273,8 +10273,9 @@ - - + + + diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index 4921e669..8553316d 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -3647,7 +3647,7 @@ Tests\Chrono - + Tests\Callbacks & Delegates @@ -3704,7 +3704,7 @@ Tests\CRC - + Tests\Callbacks & Delegates @@ -3800,6 +3800,9 @@ Tests\Strings + + Tests\Callbacks & Delegates + @@ -3979,12 +3982,6 @@ Resource Files\CI\Github - - Documentation - - - Documentation - From ae6ca929c1d1936dd3d8127cd66e7c75988f51d0 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sat, 11 Apr 2026 10:10:17 +0200 Subject: [PATCH 3/7] Rename _current to _current_it in ranges.h (#1387) * Print test names at test time (#1343) * Rename _current to _current_it in ranges.h Resolves conflict with _current macro from Zephyr and improves self-explanation of variable. --------- Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove --- include/etl/ranges.h | 52 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/include/etl/ranges.h b/include/etl/ranges.h index fe62e06d..b46e92af 100644 --- a/include/etl/ranges.h +++ b/include/etl/ranges.h @@ -2960,7 +2960,7 @@ namespace etl iterator() = default; iterator(const_iterator_type current, const_iterator_type segment_end, bool is_end) - : _current(current) + : _current_it(current) , _segment_end(segment_end) , _is_end(is_end || (current == segment_end)) { @@ -2968,18 +2968,18 @@ namespace etl reference operator*() const { - return *_current; + return *_current_it; } pointer operator->() const { - return &(*_current); + return &(*_current_it); } iterator& operator++() { - ++_current; - if (_current == _segment_end) + ++_current_it; + if (_current_it == _segment_end) { _is_end = true; } @@ -3003,7 +3003,7 @@ namespace etl { return false; } - return _current == other._current; + return _current_it == other._current_it; } constexpr bool operator!=(const iterator& other) const @@ -3013,7 +3013,7 @@ namespace etl private: - const_iterator_type _current{}; + const_iterator_type _current_it{}; const_iterator_type _segment_end{}; bool _is_end = true; }; @@ -3364,7 +3364,7 @@ namespace etl concat_iterator(size_t index, concat_view& view, iterator_variant_type current) : _ranges_index{index} , _view(view) - , _current(current) + , _current_it(current) { } @@ -3372,7 +3372,7 @@ namespace etl constexpr reference operator*() const { - return _view.get_value(_ranges_index, _current); + return _view.get_value(_ranges_index, _current_it); } constexpr decltype(auto) operator[](difference_type pos) const @@ -3382,14 +3382,14 @@ namespace etl { for (difference_type i = 0; i < pos; ++i) { - tmp._view.advance(tmp._ranges_index, tmp._current, 1); + tmp._view.advance(tmp._ranges_index, tmp._current_it, 1); } } if (pos < 0) { for (difference_type i = 0; i < -pos; ++i) { - tmp._view.advance(tmp._ranges_index, tmp._current, -1); + tmp._view.advance(tmp._ranges_index, tmp._current_it, -1); } } return *tmp; @@ -3397,27 +3397,27 @@ namespace etl constexpr concat_iterator& operator++() { - _view.advance(_ranges_index, _current, 1); + _view.advance(_ranges_index, _current_it, 1); return *this; } constexpr concat_iterator operator++(int) { auto result = *this; - _view.advance(_ranges_index, _current, 1); + _view.advance(_ranges_index, _current_it, 1); return result; } constexpr concat_iterator& operator--() { - _view.advance(_ranges_index, _current, -1); + _view.advance(_ranges_index, _current_it, -1); return *this; } constexpr concat_iterator operator--(int) { auto result = *this; - _view.advance(_ranges_index, _current, -1); + _view.advance(_ranges_index, _current_it, -1); return result; } @@ -3425,7 +3425,7 @@ namespace etl { for (difference_type i = 0; i < n; ++i) { - _view.advance(_ranges_index, _current, 1); + _view.advance(_ranges_index, _current_it, 1); } return *this; } @@ -3434,7 +3434,7 @@ namespace etl { for (difference_type i = 0; i < n; ++i) { - _view.advance(_ranges_index, _current, -1); + _view.advance(_ranges_index, _current_it, -1); } return *this; } @@ -3442,12 +3442,12 @@ namespace etl friend constexpr bool operator==(const concat_iterator& x, etl::default_sentinel_t) { return x._ranges_index == x._view.number_of_ranges - 1 - && etl::get(x._current) == etl::get(x._view).end(); + && etl::get(x._current_it) == etl::get(x._view).end(); } friend constexpr bool operator==(const concat_iterator& x, const concat_iterator& y) { - return x._ranges_index == y._ranges_index && x._current.index() == y._current.index() && x._current == y._current; + return x._ranges_index == y._ranges_index && x._current_it.index() == y._current_it.index() && x._current_it == y._current_it; } friend constexpr bool operator!=(const concat_iterator& x, etl::default_sentinel_t) @@ -3464,7 +3464,7 @@ namespace etl size_t _ranges_index; const concat_view& _view; - iterator_variant_type _current; + iterator_variant_type _current_it; }; template @@ -5731,7 +5731,7 @@ namespace etl using iterator_category = ETL_OR_STD::forward_iterator_tag; constexpr cartesian_product_iterator(iterators_type current, iterators_type begins, iterators_type ends, bool is_end = false) - : _current(current) + : _current_it(current) , _begins(begins) , _ends(ends) , _is_end(is_end) @@ -5784,7 +5784,7 @@ namespace etl template constexpr etl::enable_if_t<(I > 0)> increment_at() { - auto& it = etl::get(_current); + auto& it = etl::get(_current_it); ++it; if (it == etl::get(_ends)) { @@ -5796,7 +5796,7 @@ namespace etl template constexpr etl::enable_if_t<(I == 0)> increment_at() { - auto& it = etl::get<0>(_current); + auto& it = etl::get<0>(_current_it); ++it; if (it == etl::get<0>(_ends)) { @@ -5807,16 +5807,16 @@ namespace etl template constexpr value_type deref(etl::index_sequence) const { - return value_type(*etl::get(_current)...); + return value_type(*etl::get(_current_it)...); } template constexpr bool all_equal(const cartesian_product_iterator& other, etl::index_sequence) const { - return ((etl::get(_current) == etl::get(other._current)) && ...); + return ((etl::get(_current_it) == etl::get(other._current_it)) && ...); } - iterators_type _current; + iterators_type _current_it; iterators_type _begins; iterators_type _ends; bool _is_end; From f118c2807afcfdc31d2e95a35624070a8afe1faf Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sat, 11 Apr 2026 10:16:03 +0200 Subject: [PATCH 4/7] Fix broken syntax from clang-format reformat (#1385) * Print test names at test time (#1343) * Fix broken syntax from clang-format reformat --------- Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove --- include/etl/doxygen.h | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/include/etl/doxygen.h b/include/etl/doxygen.h index f9adce19..0251f1b6 100644 --- a/include/etl/doxygen.h +++ b/include/etl/doxygen.h @@ -27,31 +27,29 @@ SOFTWARE. ******************************************************************************/ ///\defgroup etl Embedded Template Library. -https - : // github.com/ETLCPP/etl - http - : // www.etlcpp.com +/// https://github.com/ETLCPP/etl +/// http://www.etlcpp.com - ///\defgroup containers Containers - ///\ingroup etl +///\defgroup containers Containers +///\ingroup etl - ///\defgroup utilities Utilities - /// A set of utility templates. - ///\ingroup etl +///\defgroup utilities Utilities +/// A set of utility templates. +///\ingroup etl - ///\defgroup maths Maths - /// A set of mathematical templates. - ///\ingroup etl +///\defgroup maths Maths +/// A set of mathematical templates. +///\ingroup etl - ///\defgroup patterns Patterns - /// A set of templated design patterns. - ///\ingroup etl +///\defgroup patterns Patterns +/// A set of templated design patterns. +///\ingroup etl - ///\defgroup crc CRC - /// A set of CRC calculations - ///\ingroup maths +///\defgroup crc CRC +/// A set of CRC calculations +///\ingroup maths - ///\ingroup etl - namespace etl +///\ingroup etl +namespace etl { } From beeb4cf462b748cb890fd4b9abff75bb0a981ede Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sat, 11 Apr 2026 10:38:39 +0200 Subject: [PATCH 5/7] Fix coverage workflow for action version (#1384) * Print test names at test time (#1343) * Fix coverage workflow for action version --------- Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove --- .github/workflows/coverage.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 79f9553e..5c05ebef 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -65,8 +65,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 - -# GitHub Repository settings -# -> Settings -> Pages -# -> Source: gh actions + uses: actions/deploy-pages@v5 From b14f70698f5ff309bfc49e25f96fd95beaaf0320 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Tue, 14 Apr 2026 11:48:03 +0200 Subject: [PATCH 6/7] Fix chrono.h year_month_weekday_last and year_month_weekday sysdays() (#1396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Print test names at test time (#1343) * Fix chrono.h year_month_weekday_last and year_month_weekday sysdays() Bug 1: year_month_weekday_last::operator sys_days() — wrong weekday construction The code was constructing a weekday from a raw day count using weekday(unsigned), which treats the value as a weekday encoding (0–6). The fix uses weekday(sys_days), which correctly accounts for the epoch being a Thursday (+4 offset). Bug 2: year_month_weekday::operator sys_days() — same wrong weekday construction + off-by-one in day_of_month Same weekday(unsigned) vs weekday(sys_days) issue. Additionally, the day_of_month calculation was missing the 1 + base — it computed a 0-based offset from day 1, but forgot to add the 1 back when converting to an actual day number. --------- Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove --- .../etl/private/chrono/year_month_weekday.h | 6 ++--- test/test_chrono_year_month_weekday.cpp | 26 ++++++++++++++++--- test/test_chrono_year_month_weekday_last.cpp | 2 +- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/include/etl/private/chrono/year_month_weekday.h b/include/etl/private/chrono/year_month_weekday.h index c557138a..8d7f4d52 100644 --- a/include/etl/private/chrono/year_month_weekday.h +++ b/include/etl/private/chrono/year_month_weekday.h @@ -174,11 +174,11 @@ namespace etl unsigned int target_wd = ymwd.weekday().c_encoding(); unsigned int target_index = ymwd.index(); - etl::chrono::weekday first_weekday(static_cast(sd.time_since_epoch().count())); + etl::chrono::weekday first_weekday(sd); unsigned int first_wd = first_weekday.c_encoding(); unsigned int offset = (target_wd - first_wd + 7U) % 7U; - unsigned int day_of_month = offset + (target_index - 1U) * 7U; + unsigned int day_of_month = 1U + offset + (target_index - 1U) * 7U; etl::chrono::year_month_day result(year(), month(), etl::chrono::day(day_of_month)); @@ -382,7 +382,7 @@ namespace etl { etl::chrono::year_month_day ymd(year(), month(), etl::chrono::day(d)); etl::chrono::sys_days ymd_sys_days = static_cast(ymd); - etl::chrono::weekday wd(static_cast(ymd_sys_days.time_since_epoch().count())); + etl::chrono::weekday wd(ymd_sys_days); if (wd == weekday()) { diff --git a/test/test_chrono_year_month_weekday.cpp b/test/test_chrono_year_month_weekday.cpp index 8079d813..f0ab8500 100644 --- a/test/test_chrono_year_month_weekday.cpp +++ b/test/test_chrono_year_month_weekday.cpp @@ -117,10 +117,30 @@ namespace //************************************************************************* TEST(test_to_sys_days) { - Chrono::year_month_weekday ymwd{Chrono::year(2000), Chrono::February, Chrono::weekday_indexed(Chrono::Thursday, 1)}; - Chrono::sys_days sd = Chrono::sys_days(ymwd); + // 1st Thursday of February 2000 (Feb 3) + Chrono::year_month_weekday ymwd1{Chrono::year(2000), Chrono::February, Chrono::weekday_indexed(Chrono::Thursday, 1)}; + Chrono::sys_days sd1 = Chrono::sys_days(ymwd1); + CHECK_EQUAL(10990, sd1.time_since_epoch().count()); - CHECK_EQUAL(10990, sd.time_since_epoch().count()); + // 2nd Wednesday of March 2000 (Mar 8) + Chrono::year_month_weekday ymwd2{Chrono::year(2000), Chrono::March, Chrono::weekday_indexed(Chrono::Wednesday, 2)}; + Chrono::sys_days sd2 = Chrono::sys_days(ymwd2); + CHECK_EQUAL(11024, sd2.time_since_epoch().count()); + + // 1st Sunday of January 2000 (Jan 2) + Chrono::year_month_weekday ymwd3{Chrono::year(2000), Chrono::January, Chrono::weekday_indexed(Chrono::Sunday, 1)}; + Chrono::sys_days sd3 = Chrono::sys_days(ymwd3); + CHECK_EQUAL(10958, sd3.time_since_epoch().count()); + + // 3rd Friday of June 1985 (Jun 21) + Chrono::year_month_weekday ymwd4{Chrono::year(1985), Chrono::June, Chrono::weekday_indexed(Chrono::Friday, 3)}; + Chrono::sys_days sd4 = Chrono::sys_days(ymwd4); + CHECK_EQUAL(5650, sd4.time_since_epoch().count()); + + // 2nd Wednesday of March 2024 (Mar 13) + Chrono::year_month_weekday ymwd5{Chrono::year(2024), Chrono::March, Chrono::weekday_indexed(Chrono::Wednesday, 2)}; + Chrono::sys_days sd5 = Chrono::sys_days(ymwd5); + CHECK_EQUAL(19795, sd5.time_since_epoch().count()); } //************************************************************************* diff --git a/test/test_chrono_year_month_weekday_last.cpp b/test/test_chrono_year_month_weekday_last.cpp index 7050903f..bbe92619 100644 --- a/test/test_chrono_year_month_weekday_last.cpp +++ b/test/test_chrono_year_month_weekday_last.cpp @@ -73,7 +73,7 @@ namespace Chrono::year_month_weekday_last ymwdl{Chrono::year(2000), Chrono::February, Chrono::weekday_last(Chrono::Thursday)}; Chrono::sys_days sd = Chrono::sys_days(ymwdl); - CHECK_EQUAL(11012, sd.time_since_epoch().count()); + CHECK_EQUAL(11011, sd.time_since_epoch().count()); } //************************************************************************* From f258fe4af8672d50baece68288f827f60d653514 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Tue, 14 Apr 2026 11:56:05 +0200 Subject: [PATCH 7/7] Fix operator| conflict with std::ranges (#1395) --- include/etl/ranges.h | 48 ++++++++++++++++++++++++++------------------ test/test_ranges.cpp | 8 ++++++++ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/include/etl/ranges.h b/include/etl/ranges.h index b46e92af..8e875b83 100644 --- a/include/etl/ranges.h +++ b/include/etl/ranges.h @@ -735,8 +735,23 @@ namespace etl inline constexpr private_views::repeat repeat; } // namespace views - template - class range_adapter_closure + // Non-template base so ADL finds exactly one operator| definition. + class range_adapter_closure_base + { + // Found via ADL on the RHS (any type that derives from + // range_adapter_closure_base). Placing the operator here instead of at + // global/namespace scope avoids conflicts with std::ranges::operator|. + template > + && etl::is_invocable_v, Range>>> + friend auto operator|(Range&& r, Closure&& c) + { + return etl::forward(c)(etl::forward(r)); + } + }; + + template + class range_adapter_closure : public range_adapter_closure_base { }; @@ -932,7 +947,7 @@ namespace etl { namespace private_views { - struct all + struct all : public range_adapter_closure_base { template < class Range, etl::enable_if_t>, etl::decay_t>, int> = 0> @@ -1422,7 +1437,7 @@ namespace etl { namespace private_views { - struct as_rvalue + struct as_rvalue : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -1501,7 +1516,7 @@ namespace etl { namespace private_views { - struct as_const + struct as_const : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -1726,7 +1741,7 @@ namespace etl { namespace private_views { - struct cache_latest + struct cache_latest : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -1802,7 +1817,7 @@ namespace etl { namespace private_views { - struct reverse + struct reverse : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -2397,7 +2412,7 @@ namespace etl { namespace private_views { - struct join + struct join : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -4244,7 +4259,7 @@ namespace etl { namespace private_views { - struct common + struct common : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -4404,7 +4419,7 @@ namespace etl { namespace private_views { - struct enumerate + struct enumerate : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -4569,7 +4584,7 @@ namespace etl namespace private_views { template - struct elements_fn + struct elements_fn : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -4797,7 +4812,7 @@ namespace etl namespace private_views { template - struct adjacent_fn + struct adjacent_fn : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -6064,7 +6079,7 @@ namespace etl { namespace private_views { - struct to_input + struct to_input : public range_adapter_closure_base { template constexpr auto operator()(Range&& r) const @@ -6152,13 +6167,6 @@ namespace etl namespace views = ranges::views; } // namespace etl -template < class Range, typename RangeAdaptorClosure, typename = etl::enable_if_t>> - -auto operator|(Range&& r, RangeAdaptorClosure rac) -{ - return rac(etl::forward(r)); -} - #endif #endif diff --git a/test/test_ranges.cpp b/test/test_ranges.cpp index fff3d9bc..0fe4c41b 100644 --- a/test/test_ranges.cpp +++ b/test/test_ranges.cpp @@ -37,6 +37,14 @@ SOFTWARE. #include #include +// Issue #1391: include when available to verify etl::operator| +// does not conflict with std::ranges::operator|. +#if ETL_USING_STL && ETL_USING_CPP20 && defined(__has_include) + #if __has_include() + #include + #endif +#endif + #if ETL_USING_CPP17 // C++03 does not support move semantics as used in the ranges library