From ffea27142ae6dbf00e85b15c4c1fba2e96494a93 Mon Sep 17 00:00:00 2001 From: Drew Rife Date: Tue, 8 Sep 2026 14:45:31 -0400 Subject: [PATCH] Support runtime floating-point clamped_value before C++20 (#1595) --- docs/utilities/clamped-value.md | 43 ++++++++-------- include/etl/clamped_value.h | 44 +++++++++++++---- test/syntax_check/clamped_value.h.t.cpp | 7 ++- test/test_clamped_value.cpp | 66 ++++++++++++++++--------- 4 files changed, 105 insertions(+), 55 deletions(-) diff --git a/docs/utilities/clamped-value.md b/docs/utilities/clamped-value.md index 18fba437..5bae0543 100644 --- a/docs/utilities/clamped-value.md +++ b/docs/utilities/clamped-value.md @@ -10,26 +10,27 @@ title: "clamped_value" Provides a value constrained between a minimum and maximum value. Values are clamped using `etl::clamp`. The type supports compile-time bounds to minimise storage and runtime bounds when the range must be changed. Integral types are -supported in all language modes. Floating-point types are supported in C++20 -when `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is `1`. By default, the macro is -enabled when `__cpp_nontype_template_args` is at least `201911L`. +supported in all language modes. Floating-point types with runtime bounds are +supported in C++11 and later when `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is +`1`. Floating-point compile-time bounds require C++20 support for +floating-point non-type template arguments. ## Availability `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is always defined by this header as -either `0` or `1`. It defaults to `1` only when C++20 is enabled and the -compiler supports floating-point non-type template arguments. A platform -profile or build may define the macro before including the header to override -automatic detection. +either `0` or `1`. It defaults to `1` in C++11 and later. The related +`ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE` macro defaults to `1` only +when C++20 is enabled and the compiler supports floating-point non-type +template arguments. A platform profile or build may define either macro before +including the header to override automatic detection. -When the macro is `0`, `T` must be an integral type. When it is `1`, `float`, -`double`, and `long double` may also be used. +When `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is `0`, `T` must be an integral +type. When it is `1`, `float`, `double`, and `long double` may also be used with +runtime bounds. ```cpp -template +// C++11 and later +template class clamped_value; ``` @@ -39,8 +40,11 @@ etl::clamped_value value_ct; // Fixed range [2, 7]. etl::clamped_value value_rt(2, 7, 5); // Runtime range and initial value. #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE +etl::clamped_value float_rt(-1.5, 2.5, 0.5); +#endif + +#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE etl::clamped_value float_ct(0.5f); -etl::clamped_value float_rt(-1.5, 2.5, 0.5); #endif ``` @@ -67,13 +71,10 @@ clamped_value value(min, max, initial); Creates a runtime clamped value. The two-argument form initializes the value to `min`; the three-argument form clamps `initial` to the range. -`min` must not be greater than `max`. As with `cyclic_value`, zero-initialized -`Min` and `Max` select the runtime-bound specialization; use a non-zero -compile-time bound when a fixed compile-time range is needed. NaN bounds and -initial values are rejected. Infinite bounds and values are permitted. - -Because zero-initialized `Min` and `Max` select runtime bounds, a fixed -compile-time range `[0, 0]` cannot be represented by this dispatch scheme. +`min` must not be greater than `max`. Omitting the template bounds selects the +runtime-bound specialization. Supplying `Min` and `Max` selects fixed +compile-time bounds. NaN bounds and initial values are rejected. Infinite +bounds and values are permitted. ## Modifiers diff --git a/include/etl/clamped_value.h b/include/etl/clamped_value.h index d587bde4..d7f3dcf9 100644 --- a/include/etl/clamped_value.h +++ b/include/etl/clamped_value.h @@ -43,21 +43,32 @@ SOFTWARE. ///\def ETL_HAS_FLOATING_POINT_CLAMPED_VALUE /// Set to `1` when clamped_value supports floating-point types. -/// Floating-point support requires C++20 non-type template arguments. +/// Runtime floating-point support requires C++11 variadic templates. /// The macro may be defined by the build or platform profile to override /// automatic detection. #if !defined(ETL_HAS_FLOATING_POINT_CLAMPED_VALUE) - #if ETL_USING_CPP20 && defined(__cpp_nontype_template_args) && (__cpp_nontype_template_args >= 201911L) + #if ETL_USING_CPP11 #define ETL_HAS_FLOATING_POINT_CLAMPED_VALUE 1 #else #define ETL_HAS_FLOATING_POINT_CLAMPED_VALUE 0 #endif #endif +///\def ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE +/// Set to `1` when clamped_value supports floating-point compile-time bounds. +#if !defined(ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE) + #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE && ETL_USING_CPP20 && defined(__cpp_nontype_template_args) && (__cpp_nontype_template_args >= 201911L) + #define ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE 1 + #else + #define ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE 0 + #endif +#endif + ///\defgroup clamped_value clamped_value /// Provides a value that is clamped between two limits. /// Integral types are supported in all language modes. Floating-point types -/// are supported when ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`. +/// with runtime bounds are supported when +/// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`. /// \ingroup utilities namespace etl @@ -113,18 +124,18 @@ namespace etl return value; } - const unsigned_type current = etl::to_unsigned(value); //static_cast(value); + const unsigned_type current = etl::to_unsigned(value); // static_cast(value); const unsigned_type step = etl::absolute_unsigned(n); if ((n > 0) && (value < max_value)) { - const unsigned_type distance = etl::to_unsigned(max_value) - current; //static_cast(max_value) - current; + const unsigned_type distance = etl::to_unsigned(max_value) - current; // static_cast(max_value) - current; value = (step >= distance) ? max_value : static_cast(current + step); } else if ((n < 0) && (value > min_value)) { - const unsigned_type distance = current - etl::to_unsigned(min_value); //static_cast(min_value); + const unsigned_type distance = current - etl::to_unsigned(min_value); // static_cast(min_value); value = (step >= distance) ? min_value : static_cast(current - step); } @@ -236,22 +247,32 @@ namespace etl } // namespace private_clamped_value /// \endcond -#include "private/diagnostic_float_equal_push.h" +#if ETL_USING_CPP11 + template + class clamped_value; +#else // ETL_NOT_USING_CPP11 + #include "private/diagnostic_float_equal_push.h" template class clamped_value; -#include "private/diagnostic_pop.h" + #include "private/diagnostic_pop.h" +#endif //*************************************************************************** /// Provides a value that is clamped between two compile-time limits. /// Supports incrementing, decrementing and arbitrary advance. ///@tparam T An integral type, or a floating-point type when - /// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`. + /// ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE is `1`. ///@tparam Min The minimum value of the range. ///@tparam Max The maximum value of the range. ///\ingroup clamped_value //*************************************************************************** +#if ETL_USING_CPP11 + template + class clamped_value +#else // ETL_NOT_USING_CPP11 template class clamped_value +#endif { public: @@ -583,8 +604,13 @@ namespace etl ///@tparam Max Ignored for this specialisation. ///\ingroup clamped_value //*************************************************************************** +#if ETL_USING_CPP11 + template + class clamped_value +#else // ETL_NOT_USING_CPP11 template class clamped_value +#endif { public: diff --git a/test/syntax_check/clamped_value.h.t.cpp b/test/syntax_check/clamped_value.h.t.cpp index a09da520..7651ff8c 100644 --- a/test/syntax_check/clamped_value.h.t.cpp +++ b/test/syntax_check/clamped_value.h.t.cpp @@ -31,6 +31,9 @@ SOFTWARE. #include #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE -etl::clamped_value clamped_float_compile_time; -etl::clamped_value clamped_double_run_time; +etl::clamped_value clamped_double_run_time; +#endif + +#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE +etl::clamped_value clamped_float_compile_time; #endif diff --git a/test/test_clamped_value.cpp b/test/test_clamped_value.cpp index 40ecc270..cdf81420 100644 --- a/test/test_clamped_value.cpp +++ b/test/test_clamped_value.cpp @@ -61,6 +61,17 @@ namespace CHECK_EQUAL(7, value.max()); } +#if ETL_USING_CPP11 + //************************************************************************* + TEST(test_compile_time_zero_range) + { + etl::clamped_value value(1); + CHECK_EQUAL(0, value.get()); + CHECK_EQUAL(0, value.min()); + CHECK_EQUAL(0, value.max()); + } +#endif + //************************************************************************* TEST(test_run_time_initialisation) { @@ -364,7 +375,7 @@ namespace CHECK_THROW(value.set(7, 2), etl::exception); } -#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE +#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE //************************************************************************* TEST(test_compile_time_floating_clamps_and_advances) { @@ -381,7 +392,9 @@ namespace value.advance(10.0f); CHECK_CLOSE(2.5f, value.get(), 0.0001f); } +#endif +#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE //************************************************************************* TEST(test_runtime_floating_clamps_and_advances) { @@ -396,45 +409,52 @@ namespace value.advance(-10.0); CHECK_CLOSE(-1.5, value.get(), 0.0001); } +#endif +#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE //************************************************************************* - TEST(test_floating_increment_and_decrement_saturate_at_fractional_bounds) + TEST(test_compile_time_floating_increment_saturates_at_fractional_bound) { etl::clamped_value compile_time(0.25f); - etl::clamped_value run_time(-0.5f, 0.5f, -0.25f); ++compile_time; - --run_time; CHECK_CLOSE(0.5f, compile_time.get(), 0.0001f); - CHECK_CLOSE(-0.5f, run_time.get(), 0.0001f); + } +#endif + +#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE + //************************************************************************* + TEST(test_runtime_floating_decrement_saturates_at_fractional_bound) + { + etl::clamped_value value(-0.5f, 0.5f, -0.25f); + + --value; + + CHECK_CLOSE(-0.5f, value.get(), 0.0001f); } //************************************************************************* - TEST(test_floating_comparison_and_swap) + TEST(test_runtime_floating_comparison_and_swap) { - etl::clamped_value compile_time1(-0.5); - etl::clamped_value compile_time2(1.5); - etl::clamped_value run_time1(-3.0, 3.0, -1.0); - etl::clamped_value run_time2(4.0, 8.0, 6.0); + etl::clamped_value value1(-3.0, 3.0, -1.0); + etl::clamped_value value2(4.0, 8.0, 6.0); - CHECK(compile_time1 < compile_time2); - CHECK(compile_time1 < 1.0); - swap(compile_time1, compile_time2); - swap(run_time1, run_time2); + CHECK(value1 < value2); + CHECK(value1 < 1.0); + swap(value1, value2); - CHECK_CLOSE(1.5, compile_time1.get(), 0.0001); - CHECK_CLOSE(6.0, run_time1.get(), 0.0001); - CHECK_CLOSE(4.0, run_time1.min(), 0.0001); - CHECK_CLOSE(-1.0, run_time2.get(), 0.0001); + CHECK_CLOSE(6.0, value1.get(), 0.0001); + CHECK_CLOSE(4.0, value1.min(), 0.0001); + CHECK_CLOSE(-1.0, value2.get(), 0.0001); } //************************************************************************* - TEST(test_floating_infinity_saturates) + TEST(test_runtime_floating_infinity_saturates) { - const double infinity = etl::numeric_limits::infinity(); - etl::clamped_value value(0.0); - etl::clamped_value infinite_range(-infinity, infinity, 0.0); + const double infinity = etl::numeric_limits::infinity(); + etl::clamped_value value(-2.0, 2.0, 0.0); + etl::clamped_value infinite_range(-infinity, infinity, 0.0); value += infinity; CHECK_CLOSE(2.0, value.get(), 0.0001); @@ -501,7 +521,7 @@ namespace CHECK(true); } - #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE && (ETL_USING_MICROSOFT_COMPILER && (_MSC_VER >= 1950)) + #if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE && (ETL_USING_MICROSOFT_COMPILER && (_MSC_VER >= 1950)) //************************************************************************* TEST(test_floating_clamped_value_constexpr) {