diff --git a/Eigen/src/Core/StructuredBindings.h b/Eigen/src/Core/StructuredBindings.h index f11720ba6..676136028 100644 --- a/Eigen/src/Core/StructuredBindings.h +++ b/Eigen/src/Core/StructuredBindings.h @@ -30,51 +30,58 @@ // // Decomposition order follows storage order: column-major by default, // so Matrix2d decomposes as (0,0), (1,0), (0,1), (1,1). Only fixed-size -// column-major Matrix and Array specialize here; Map, Ref, and fixed-size +// column-major Matrix and Array are tuple-like; Map, Ref, and fixed-size // Block intentionally do not participate. +namespace Eigen { +namespace internal { + +// Bases supplying value/type only for fixed-size shapes, leaving the std +// specializations below memberless for dynamic sizes: generic tuple-like +// detection probes std::tuple_size::value in a SFINAE context (fmt's range +// formatter, issue #3103) and needs a substitution failure, not a hard error. +// An enable_if_t base-clause cannot replace this: base-class-specifier +// substitution is not a SFINAE context. +template +struct structured_binding_size {}; + +template +struct structured_binding_size + : std::integral_constant(Rows_) * static_cast(Cols_)> {}; + +// Note: uses Idx_ instead of I to avoid conflict with Eigen's test framework macro. +template +struct structured_binding_element {}; + +template +struct structured_binding_element { + static_assert(Idx_ < static_cast(Rows_) * static_cast(Cols_), "Index out of range."); + using type = Scalar_; +}; + +} // namespace internal +} // namespace Eigen + namespace std { -// std::tuple_size for fixed-size Matrix. -// -// Deliberately NOT SFINAE-gated on (Rows, Cols) because base-class-specifier -// substitution is not a SFINAE context (a malformed base via enable_if_t -// produces a non-SFINAE hard error rather than letting the primary template -// stay incomplete). The static_assert below produces a friendly diagnostic -// if generic code probes tuple_size. +// std::tuple_size / std::tuple_element for Matrix. template struct tuple_size> - : std::integral_constant((Rows_ > 0 && Cols_ > 0) ? Rows_* Cols_ : 0)> { - static_assert(Rows_ != Eigen::Dynamic && Cols_ != Eigen::Dynamic, - "Structured bindings require fixed-size Eigen types (e.g. Vector3d, not VectorXd)."); -}; + : Eigen::internal::structured_binding_size {}; -// std::tuple_element for fixed-size Matrix. -// Note: uses Idx_ instead of I to avoid conflict with Eigen's test framework macro. template -struct tuple_element> { - static_assert(Rows_ != Eigen::Dynamic && Cols_ != Eigen::Dynamic, - "Structured bindings require fixed-size Eigen types (e.g. Vector3d, not VectorXd)."); - static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); - using type = Scalar_; -}; +struct tuple_element> + : Eigen::internal::structured_binding_element {}; -// std::tuple_size for fixed-size Array. See note on Matrix specialization above. +// std::tuple_size / std::tuple_element for Array. template struct tuple_size> - : std::integral_constant((Rows_ > 0 && Cols_ > 0) ? Rows_* Cols_ : 0)> { - static_assert(Rows_ != Eigen::Dynamic && Cols_ != Eigen::Dynamic, - "Structured bindings require fixed-size Eigen types (e.g. Array3d, not ArrayXd)."); -}; + : Eigen::internal::structured_binding_size {}; -// std::tuple_element for fixed-size Array. template -struct tuple_element> { - static_assert(Rows_ != Eigen::Dynamic && Cols_ != Eigen::Dynamic, - "Structured bindings require fixed-size Eigen types (e.g. Array3d, not ArrayXd)."); - static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); - using type = Scalar_; -}; +struct tuple_element> + : Eigen::internal::structured_binding_element {}; } // namespace std diff --git a/failtest/structured_bindings_dynamic_array.cpp b/failtest/structured_bindings_dynamic_array.cpp index 14abe98ed..502484a9b 100644 --- a/failtest/structured_bindings_dynamic_array.cpp +++ b/failtest/structured_bindings_dynamic_array.cpp @@ -1,8 +1,8 @@ #include "../Eigen/Core" // Reproduces the "Dynamic-sized Array breaks tuple_size" bug: the Array -// specialization had the same enable_if_t base-clause issue as Matrix. Compile -// must fail for ArrayXd. +// specialization behaves like the Matrix one — a SFINAE-friendly empty +// tuple_size (issue #3103), so naming ::value must still fail to compile. #ifdef EIGEN_SHOULD_FAIL_TO_BUILD #define ROWS Eigen::Dynamic #define COLS Eigen::Dynamic diff --git a/failtest/structured_bindings_dynamic_matrix.cpp b/failtest/structured_bindings_dynamic_matrix.cpp index 784d48ed1..687e41cd4 100644 --- a/failtest/structured_bindings_dynamic_matrix.cpp +++ b/failtest/structured_bindings_dynamic_matrix.cpp @@ -1,9 +1,8 @@ #include "../Eigen/Core" // Reproduces the "Dynamic-sized Matrix breaks tuple_size" bug reported on !2336. -// With an enable_if_t base-clause the error is a cryptic non-SFINAE hard error; -// with our static_assert-in-body fix it becomes a friendly diagnostic. Either -// way, the compile must fail for MatrixXd. +// tuple_size is a SFINAE-friendly empty specialization (issue #3103), +// so naming ::value outside a SFINAE context must still fail to compile. #ifdef EIGEN_SHOULD_FAIL_TO_BUILD #define ROWS Eigen::Dynamic #define COLS Eigen::Dynamic diff --git a/test/structured_bindings.cpp b/test/structured_bindings.cpp index 7676cb26d..5fbb0e407 100644 --- a/test/structured_bindings.cpp +++ b/test/structured_bindings.cpp @@ -188,6 +188,36 @@ void check_tuple_element() { STATIC_CHECK((std::is_same, int>::value)); } +// Emulates generic tuple-like detection as done by fmt's range formatter +// (issue #3103): probing tuple_size::value in a SFINAE context must be a +// substitution failure for dynamic-size types, not a hard error. +template +struct is_tuple_like : std::false_type {}; + +template +struct is_tuple_like::value)>> : std::true_type {}; + +template +struct has_tuple_element0 : std::false_type {}; + +template +struct has_tuple_element0::type>> : std::true_type {}; + +void check_sfinae_friendly_detection() { + STATIC_CHECK((is_tuple_like::value)); + STATIC_CHECK((is_tuple_like::value)); + STATIC_CHECK((is_tuple_like::value)); + STATIC_CHECK((!is_tuple_like::value)); + STATIC_CHECK((!is_tuple_like::value)); + STATIC_CHECK((!is_tuple_like::value)); + STATIC_CHECK((!is_tuple_like>::value)); + STATIC_CHECK((!is_tuple_like>::value)); + STATIC_CHECK((has_tuple_element0::value)); + STATIC_CHECK((has_tuple_element0::value)); + STATIC_CHECK((!has_tuple_element0::value)); + STATIC_CHECK((!has_tuple_element0::value)); +} + EIGEN_DECLARE_TEST(structured_bindings) { CALL_SUBTEST_1(check_vector_bindings()); CALL_SUBTEST_1(check_vector_bindings()); @@ -200,6 +230,7 @@ EIGEN_DECLARE_TEST(structured_bindings) { CALL_SUBTEST_4(check_matrix_bindings()); CALL_SUBTEST_5(check_tuple_size()); CALL_SUBTEST_5(check_tuple_element()); + CALL_SUBTEST_5(check_sfinae_friendly_detection()); CALL_SUBTEST_6(check_storage_order_semantics()); CALL_SUBTEST_6(check_storage_order_semantics()); }