From 188639717b4133c3c89aee1367871203f05422aa Mon Sep 17 00:00:00 2001 From: Samaresh Kumar Singh Date: Sun, 26 Apr 2026 14:19:37 -0500 Subject: [PATCH 1/2] Add initializer_list constructor for span (P4190R0) P2447R6 originally added an il constructor for C++26 but it caused silent behavior changes for span{ptr, size} (the (bool*, size_t) arguments would convert to two bools and grab the il ctor). P4144R1 reverted it for that reason. P4190R0 brings it back with the right constraints to avoid the regression: - is_const_v: the il's underlying array is a temporary, so only span can sensibly observe it - same_as: exact match, no conversions - this is what stops the (ptr, size) -> bool gotcha from happening The implementation uses a dummy class T_ = ElementType template parameter. So is_const_v stays dependent on a function template parameter. Without it the check would be non-dependent and hard-error inside span rather than just SFINAE-out. Added six new tests cover dynamic and fixed extent construction (using the function-call idiom since the il's underlying array is a full-expression temporary), const bool from braced-init-list, named initializer_list with extended array lifetime, pointer+size on span still resolving to the It+count ctor (the regression P4144R1 reverted), and detection-idiom checks confirming span rejects il construction and the same_as constraint blocks implicit conversions like int -> float. --- include/beman/span/span.hpp | 20 ++++++++++ tests/beman/span/span.test.cpp | 72 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index f211b8c..d7cc776 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -148,6 +149,25 @@ class span { } } + // initializer_list constructor (P4190R0). + // The same_as match (not is_convertible) is what stops span + // from silently grabbing this overload for a (bool*, size_t) argument + // pair - the bug that got the C++26 version (P2447R6) reverted by P4144R1. + // T_ defaults to ElementType so is_const_v stays dependent on a function + // template parameter; otherwise it would hard-error in span. + template , int> = 0, + std::enable_if_t>, + int> = 0> + constexpr explicit(Extent != dynamic_extent) + span(std::initializer_list il) + : data_(il.begin()), size_(il.size()) { + if constexpr (Extent != dynamic_extent) { + assert(il.size() == Extent); + } + } + // Cross-span converting constructor. template #include #include +#include #include +#include +#include #include namespace bsp = beman::span; @@ -170,6 +173,75 @@ TEST(SpanConstruction, dynamic_from_static) { EXPECT_EQ(dynamic.size(), 4u); } +TEST(SpanInitList, dynamic_from_braced_list) { + auto verify = [](bsp::span s) { + EXPECT_EQ(s.size(), 3u); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[1], 2); + EXPECT_EQ(s[2], 3); + }; + verify({1, 2, 3}); +} + +TEST(SpanInitList, fixed_extent_from_braced_list) { + auto verify = [](bsp::span s) { + EXPECT_EQ(s.size(), 3u); + EXPECT_EQ(s[0], 1); + EXPECT_EQ(s[2], 3); + }; + verify(bsp::span{1, 2, 3}); +} + +TEST(SpanInitList, const_bool_from_braced_list) { + auto verify = [](bsp::span s) { + EXPECT_EQ(s.size(), 3u); + EXPECT_TRUE(s[0]); + EXPECT_FALSE(s[1]); + EXPECT_TRUE(s[2]); + }; + verify({true, false, true}); +} + +TEST(SpanInitList, named_initializer_list_keeps_array_alive) { + std::initializer_list il = {10, 20, 30}; + bsp::span s(il); + EXPECT_EQ(s.size(), 3u); + EXPECT_EQ(s[0], 10); + EXPECT_EQ(s[2], 30); +} + +TEST(SpanInitList, pointer_and_size_resolves_to_pointer_count_ctor) { + bool data[4] = {true, false, true, false}; + bool* ptr = data; + std::size_t n = 4; + bsp::span s(ptr, n); + EXPECT_EQ(s.size(), 4u); + EXPECT_EQ(s.data(), data); +} + +namespace init_list_detail { +template +struct constructible_from_il : std::false_type {}; + +template +struct constructible_from_il()))>> + : std::true_type {}; +} + +TEST(SpanInitList, non_const_element_type_rejected) { + static_assert(init_list_detail::constructible_from_il< + bsp::span, std::initializer_list>::value); + static_assert(!init_list_detail::constructible_from_il< + bsp::span, std::initializer_list>::value); +} + +TEST(SpanInitList, exact_type_match_required) { + static_assert(!init_list_detail::constructible_from_il< + bsp::span, std::initializer_list>::value); + static_assert(!init_list_detail::constructible_from_il< + bsp::span, std::initializer_list>::value); +} + // --------------------------------------------------------------------------- // Element access // --------------------------------------------------------------------------- From ee236c929a49323ee354233cf9bee48c942c7e07 Mon Sep 17 00:00:00 2001 From: Samaresh Kumar Singh Date: Fri, 1 May 2026 08:15:06 -0500 Subject: [PATCH 2/2] Apply clang-format after merging main Pure formatter output. Aligns the initializer_list ctor template declarations and the at() test cases under .clang-format's AlignConsecutiveDeclarations rule. No logic changes. --- include/beman/span/span.hpp | 10 ++++----- tests/beman/span/span.test.cpp | 37 +++++++++++++++------------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/include/beman/span/span.hpp b/include/beman/span/span.hpp index 86c4ff1..cb4aa42 100644 --- a/include/beman/span/span.hpp +++ b/include/beman/span/span.hpp @@ -148,12 +148,10 @@ class span { // T_ defaults to ElementType so is_const_v stays dependent on a function // template parameter; otherwise it would hard-error in span. template , int> = 0, - std::enable_if_t>, - int> = 0> - constexpr explicit(Extent != dynamic_extent) - span(std::initializer_list il) + class T_ = ElementType, + std::enable_if_t, int> = 0, + std::enable_if_t>, int> = 0> + constexpr explicit(Extent != dynamic_extent) span(std::initializer_list il) : data_(il.begin()), size_(il.size()) { if constexpr (Extent != dynamic_extent) { assert(il.size() == Extent); diff --git a/tests/beman/span/span.test.cpp b/tests/beman/span/span.test.cpp index 6f17dd3..c676593 100644 --- a/tests/beman/span/span.test.cpp +++ b/tests/beman/span/span.test.cpp @@ -204,16 +204,16 @@ TEST(SpanInitList, const_bool_from_braced_list) { TEST(SpanInitList, named_initializer_list_keeps_array_alive) { std::initializer_list il = {10, 20, 30}; - bsp::span s(il); + bsp::span s(il); EXPECT_EQ(s.size(), 3u); EXPECT_EQ(s[0], 10); EXPECT_EQ(s[2], 30); } TEST(SpanInitList, pointer_and_size_resolves_to_pointer_count_ctor) { - bool data[4] = {true, false, true, false}; - bool* ptr = data; - std::size_t n = 4; + bool data[4] = {true, false, true, false}; + bool* ptr = data; + std::size_t n = 4; bsp::span s(ptr, n); EXPECT_EQ(s.size(), 4u); EXPECT_EQ(s.data(), data); @@ -224,22 +224,17 @@ template struct constructible_from_il : std::false_type {}; template -struct constructible_from_il()))>> - : std::true_type {}; -} +struct constructible_from_il()))>> : std::true_type {}; +} // namespace init_list_detail TEST(SpanInitList, non_const_element_type_rejected) { - static_assert(init_list_detail::constructible_from_il< - bsp::span, std::initializer_list>::value); - static_assert(!init_list_detail::constructible_from_il< - bsp::span, std::initializer_list>::value); + static_assert(init_list_detail::constructible_from_il, std::initializer_list>::value); + static_assert(!init_list_detail::constructible_from_il, std::initializer_list>::value); } TEST(SpanInitList, exact_type_match_required) { - static_assert(!init_list_detail::constructible_from_il< - bsp::span, std::initializer_list>::value); - static_assert(!init_list_detail::constructible_from_il< - bsp::span, std::initializer_list>::value); + static_assert(!init_list_detail::constructible_from_il, std::initializer_list>::value); + static_assert(!init_list_detail::constructible_from_il, std::initializer_list>::value); } // --------------------------------------------------------------------------- @@ -275,7 +270,7 @@ TEST(SpanElementAccess, write_through_span) { // at() bounds-checked access (P2821R5, C++26) TEST(SpanAt, in_bounds_returns_element) { - int arr[] = {10, 20, 30}; + int arr[] = {10, 20, 30}; bsp::span s(arr); EXPECT_EQ(s.at(0), 10); EXPECT_EQ(s.at(1), 20); @@ -283,7 +278,7 @@ TEST(SpanAt, in_bounds_returns_element) { } TEST(SpanAt, returns_lvalue_reference_for_mutable_span) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); s.at(1) = 42; EXPECT_EQ(arr[1], 42); @@ -291,13 +286,13 @@ TEST(SpanAt, returns_lvalue_reference_for_mutable_span) { } TEST(SpanAt, throws_out_of_range_at_size_boundary) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); EXPECT_THROW(s.at(3), std::out_of_range); } TEST(SpanAt, throws_out_of_range_well_past_size) { - int arr[] = {1, 2, 3}; + int arr[] = {1, 2, 3}; bsp::span s(arr); EXPECT_THROW(s.at(100), std::out_of_range); } @@ -308,14 +303,14 @@ TEST(SpanAt, empty_span_always_throws) { } TEST(SpanAt, fixed_extent_in_bounds_and_out_of_range) { - int arr[] = {7, 8, 9, 10}; + int arr[] = {7, 8, 9, 10}; bsp::span s(arr); EXPECT_EQ(s.at(3), 10); EXPECT_THROW(s.at(4), std::out_of_range); } TEST(SpanAt, const_span_returns_reference_to_const) { - const int arr[] = {1, 2, 3}; + const int arr[] = {1, 2, 3}; bsp::span s(arr); EXPECT_EQ(s.at(2), 3); static_assert(std::is_same_v);