diff --git a/Eigen/Core b/Eigen/Core index 424396f8a..2fec8f35c 100644 --- a/Eigen/Core +++ b/Eigen/Core @@ -361,6 +361,7 @@ using std::ptrdiff_t; #include "src/Core/PlainObjectBase.h" #include "src/Core/Matrix.h" #include "src/Core/Array.h" +#include "src/Core/StructuredBindings.h" #include "src/Core/Fill.h" #include "src/Core/CwiseTernaryOp.h" #include "src/Core/CwiseBinaryOp.h" diff --git a/Eigen/src/Core/StructuredBindings.h b/Eigen/src/Core/StructuredBindings.h new file mode 100644 index 000000000..073f10e20 --- /dev/null +++ b/Eigen/src/Core/StructuredBindings.h @@ -0,0 +1,155 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2026 Pavel Guzenfeld +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef EIGEN_STRUCTURED_BINDINGS_H +#define EIGEN_STRUCTURED_BINDINGS_H + +// IWYU pragma: private +#include "./InternalHeaderCheck.h" + +#if EIGEN_MAX_CPP_VER >= 17 && EIGEN_COMP_CXXVER >= 17 + +#include +#include + +// Structured bindings support for fixed-size Eigen vectors and matrices. +// +// Enables: +// Eigen::Vector3d v(1, 2, 3); +// auto [x, y, z] = v; +// +// Eigen::Array3i a(4, 5, 6); +// auto& [a0, a1, a2] = a; +// +// 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 +// Block intentionally do not participate. + +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. +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)."); +}; + +// 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_; +}; + +// std::tuple_size for fixed-size Array. See note on Matrix specialization above. +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)."); +}; + +// 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_; +}; + +} // namespace std + +namespace Eigen { + +// Until the decomposition order for genuinely 2D RowMajor storage is agreed +// upon, reject row-major matrices at the get level. coeffRef(Index) is +// linear in storage order, so permitting both orientations for 2D types would +// silently flip the binding order between Matrix and Matrix. +// Vector types (Rows==1 or Cols==1) are unaffected because storage order +// does not change element order for a 1×N or N×1 shape — and Eigen forces +// row-major on 1×N regardless, so we must allow it for row vectors. +#define EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(ROWS, COLS, OPTIONS) \ + static_assert((ROWS) == 1 || (COLS) == 1 || ((OPTIONS) & Eigen::RowMajorBit) == 0, \ + "Structured bindings on 2D RowMajor Eigen types are not supported: coeffRef(Index) follows " \ + "storage order, so decomposition order would silently flip versus the column-major default. " \ + "Use a column-major type, or transpose first. Row/column vectors are unaffected.") + +// get free functions for Matrix. +template +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_& get( + Matrix& m) noexcept { + static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types."); + static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); + EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_); + return m.coeffRef(static_cast(Idx_)); +} + +template +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const Scalar_& get( + const Matrix& m) noexcept { + static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types."); + static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); + EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_); + return m.coeffRef(static_cast(Idx_)); +} + +template +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_&& get( + Matrix&& m) noexcept { + static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types."); + static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); + EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_); + return std::move(m.coeffRef(static_cast(Idx_))); +} + +// get free functions for Array. +template +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_& get( + Array& a) noexcept { + static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types."); + static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); + EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_); + return a.coeffRef(static_cast(Idx_)); +} + +template +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const Scalar_& get( + const Array& a) noexcept { + static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types."); + static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); + EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_); + return a.coeffRef(static_cast(Idx_)); +} + +template +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_&& get( + Array&& a) noexcept { + static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types."); + static_assert(Idx_ < static_cast(Rows_ * Cols_), "Index out of range."); + EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_); + return std::move(a.coeffRef(static_cast(Idx_))); +} + +} // namespace Eigen + +#endif // C++17 + +#endif // EIGEN_STRUCTURED_BINDINGS_H diff --git a/failtest/CMakeLists.txt b/failtest/CMakeLists.txt index 107c9e2c9..0e55c0b82 100644 --- a/failtest/CMakeLists.txt +++ b/failtest/CMakeLists.txt @@ -66,3 +66,20 @@ ei_add_failtest("eigensolver_int") ei_add_failtest("eigensolver_cplx") ei_add_failtest("initializer_list_1") ei_add_failtest("initializer_list_2") + +ei_add_failtest("structured_bindings_dynamic_matrix") +ei_add_failtest("structured_bindings_dynamic_array") +ei_add_failtest("structured_bindings_rowmajor") + +# Structured bindings require C++17 — the header is a no-op otherwise and the +# "_ok" targets would not compile. Force the standard on these four targets +# (two per failtest: the _ok and _ko variants). +foreach(_sb_target + structured_bindings_dynamic_matrix_ok structured_bindings_dynamic_matrix_ko + structured_bindings_dynamic_array_ok structured_bindings_dynamic_array_ko + structured_bindings_rowmajor_ok structured_bindings_rowmajor_ko) + set_target_properties(${_sb_target} PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF) +endforeach() diff --git a/failtest/structured_bindings_dynamic_array.cpp b/failtest/structured_bindings_dynamic_array.cpp new file mode 100644 index 000000000..1986e5c84 --- /dev/null +++ b/failtest/structured_bindings_dynamic_array.cpp @@ -0,0 +1,16 @@ +#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. +#ifdef EIGEN_SHOULD_FAIL_TO_BUILD +#define ROWS Eigen::Dynamic +#define COLS Eigen::Dynamic +#else +#define ROWS 3 +#define COLS 1 +#endif + +#include + +int main() { return static_cast(std::tuple_size>::value); } diff --git a/failtest/structured_bindings_dynamic_matrix.cpp b/failtest/structured_bindings_dynamic_matrix.cpp new file mode 100644 index 000000000..b57111278 --- /dev/null +++ b/failtest/structured_bindings_dynamic_matrix.cpp @@ -0,0 +1,17 @@ +#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. +#ifdef EIGEN_SHOULD_FAIL_TO_BUILD +#define ROWS Eigen::Dynamic +#define COLS Eigen::Dynamic +#else +#define ROWS 3 +#define COLS 1 +#endif + +#include + +int main() { return static_cast(std::tuple_size>::value); } diff --git a/failtest/structured_bindings_rowmajor.cpp b/failtest/structured_bindings_rowmajor.cpp new file mode 100644 index 000000000..a3c502abe --- /dev/null +++ b/failtest/structured_bindings_rowmajor.cpp @@ -0,0 +1,17 @@ +#include "../Eigen/Core" + +// Reproduces the "storage-order footgun" on !2336: get() uses coeffRef(Index), +// which is linear in storage order, so a RowMajor matrix would silently flip +// decomposition order vs. the column-major default. We static_assert against +// RowMajor until the semantics are agreed upon — this failtest guards that. +#ifdef EIGEN_SHOULD_FAIL_TO_BUILD +#define STORAGE_ORDER Eigen::RowMajor +#else +#define STORAGE_ORDER Eigen::ColMajor +#endif + +int main() { + Eigen::Matrix m; + m << 1, 2, 3, 4; + return static_cast(Eigen::get<0>(m)); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b362f1fca..8d551b0f7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -338,6 +338,7 @@ ei_add_test(initializer_list_construction) ei_add_test(diagonal_matrix_variadic_ctor) ei_add_test(serializer) ei_add_test(tuple_test) +ei_add_test(structured_bindings) ei_add_test(threads_eventcount "-pthread" "${CMAKE_THREAD_LIBS_INIT}") ei_add_test(threads_runqueue "-pthread" "${CMAKE_THREAD_LIBS_INIT}") ei_add_test(threads_non_blocking_thread_pool "-pthread" "${CMAKE_THREAD_LIBS_INIT}") diff --git a/test/structured_bindings.cpp b/test/structured_bindings.cpp new file mode 100644 index 000000000..e49788491 --- /dev/null +++ b/test/structured_bindings.cpp @@ -0,0 +1,213 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2026 Pavel Guzenfeld +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "main.h" + +#if EIGEN_MAX_CPP_VER >= 17 && EIGEN_COMP_CXXVER >= 17 + +template +void check_vector_bindings() { + // Vector2 + { + Matrix v; + v << Scalar(1), Scalar(2); + auto [x, y] = v; + VERIFY_IS_EQUAL(x, Scalar(1)); + VERIFY_IS_EQUAL(y, Scalar(2)); + } + + // Vector3 + { + Matrix v; + v << Scalar(3), Scalar(4), Scalar(5); + auto [x, y, z] = v; + VERIFY_IS_EQUAL(x, Scalar(3)); + VERIFY_IS_EQUAL(y, Scalar(4)); + VERIFY_IS_EQUAL(z, Scalar(5)); + } + + // Vector4 + { + Matrix v; + v << Scalar(6), Scalar(7), Scalar(8), Scalar(9); + auto [a, b, c, d] = v; + VERIFY_IS_EQUAL(a, Scalar(6)); + VERIFY_IS_EQUAL(b, Scalar(7)); + VERIFY_IS_EQUAL(c, Scalar(8)); + VERIFY_IS_EQUAL(d, Scalar(9)); + } + + // 1x1 matrix (scalar-like) + { + Matrix s; + s << Scalar(42); + auto [val] = s; + VERIFY_IS_EQUAL(val, Scalar(42)); + } + + // RowVector + { + Matrix rv; + rv << Scalar(10), Scalar(20), Scalar(30); + auto [a, b, c] = rv; + VERIFY_IS_EQUAL(a, Scalar(10)); + VERIFY_IS_EQUAL(b, Scalar(20)); + VERIFY_IS_EQUAL(c, Scalar(30)); + } +} + +template +void check_array_bindings() { + // Array3 + { + Array a; + a << Scalar(100), Scalar(200), Scalar(300); + auto [x, y, z] = a; + VERIFY_IS_EQUAL(x, Scalar(100)); + VERIFY_IS_EQUAL(y, Scalar(200)); + VERIFY_IS_EQUAL(z, Scalar(300)); + } + + // Array2 + { + Array a; + a << Scalar(10), Scalar(20); + auto [x, y] = a; + VERIFY_IS_EQUAL(x, Scalar(10)); + VERIFY_IS_EQUAL(y, Scalar(20)); + } +} + +template +void check_reference_bindings() { + // Mutable reference binding + { + Matrix v; + v << Scalar(1), Scalar(2), Scalar(3); + auto& [x, y, z] = v; + x = Scalar(10); + y = Scalar(20); + z = Scalar(30); + VERIFY_IS_EQUAL(v(0), Scalar(10)); + VERIFY_IS_EQUAL(v(1), Scalar(20)); + VERIFY_IS_EQUAL(v(2), Scalar(30)); + } + + // Const reference binding + { + const Matrix v(Scalar(4), Scalar(5), Scalar(6)); + const auto& [x, y, z] = v; + VERIFY_IS_EQUAL(x, Scalar(4)); + VERIFY_IS_EQUAL(y, Scalar(5)); + VERIFY_IS_EQUAL(z, Scalar(6)); + } + + // Array mutable reference binding + { + Array a; + a << Scalar(7), Scalar(8); + auto& [x, y] = a; + x = Scalar(70); + VERIFY_IS_EQUAL(a(0), Scalar(70)); + VERIFY_IS_EQUAL(a(1), Scalar(8)); + } +} + +template +void check_matrix_bindings() { + // 2x2 matrix (column-major order) + { + Matrix m; + m << Scalar(1), Scalar(2), Scalar(3), Scalar(4); + auto [m00, m10, m01, m11] = m; + // Column-major: (0,0), (1,0), (0,1), (1,1) + VERIFY_IS_EQUAL(m00, Scalar(1)); + VERIFY_IS_EQUAL(m10, Scalar(3)); + VERIFY_IS_EQUAL(m01, Scalar(2)); + VERIFY_IS_EQUAL(m11, Scalar(4)); + } +} + +template +void check_storage_order_semantics() { + // Row vectors are forced to RowMajor by Eigen (a 1xN can't meaningfully be + // column-major). Ensure decomposition still matches the single-row layout. + { + Matrix rv; + rv << Scalar(1), Scalar(2), Scalar(3); + auto [a, b, c] = rv; + VERIFY_IS_EQUAL(a, Scalar(1)); + VERIFY_IS_EQUAL(b, Scalar(2)); + VERIFY_IS_EQUAL(c, Scalar(3)); + } + // Nx1 column vectors are ColMajor regardless and decompose top-to-bottom. + { + Matrix cv; + cv << Scalar(4), Scalar(5), Scalar(6); + auto [a, b, c] = cv; + VERIFY_IS_EQUAL(a, Scalar(4)); + VERIFY_IS_EQUAL(b, Scalar(5)); + VERIFY_IS_EQUAL(c, Scalar(6)); + } + // 2D ColMajor matrix decomposes in column-major order: (0,0),(1,0),(0,1),(1,1). + // 2D RowMajor is rejected via static_assert — see failtest/structured_bindings_rowmajor.cpp. + { + Matrix m; + m << Scalar(1), Scalar(2), Scalar(3), Scalar(4); + auto [m00, m10, m01, m11] = m; + VERIFY_IS_EQUAL(m00, Scalar(1)); + VERIFY_IS_EQUAL(m10, Scalar(3)); + VERIFY_IS_EQUAL(m01, Scalar(2)); + VERIFY_IS_EQUAL(m11, Scalar(4)); + } +} + +void check_tuple_size() { + STATIC_CHECK((std::tuple_size::value == 2)); + STATIC_CHECK((std::tuple_size::value == 3)); + STATIC_CHECK((std::tuple_size::value == 4)); + STATIC_CHECK((std::tuple_size::value == 4)); + STATIC_CHECK((std::tuple_size::value == 9)); + STATIC_CHECK((std::tuple_size::value == 3)); + STATIC_CHECK((std::tuple_size>::value == 2)); + STATIC_CHECK((std::tuple_size>::value == 1)); + STATIC_CHECK((std::tuple_size::value == 3)); +} + +void check_tuple_element() { + STATIC_CHECK((std::is_same, double>::value)); + STATIC_CHECK((std::is_same, float>::value)); + STATIC_CHECK((std::is_same, int>::value)); + STATIC_CHECK((std::is_same, int>::value)); +} + +EIGEN_DECLARE_TEST(structured_bindings) { + CALL_SUBTEST_1(check_vector_bindings()); + CALL_SUBTEST_1(check_vector_bindings()); + CALL_SUBTEST_1(check_vector_bindings()); + CALL_SUBTEST_2(check_array_bindings()); + CALL_SUBTEST_2(check_array_bindings()); + CALL_SUBTEST_3(check_reference_bindings()); + CALL_SUBTEST_3(check_reference_bindings()); + CALL_SUBTEST_4(check_matrix_bindings()); + CALL_SUBTEST_4(check_matrix_bindings()); + CALL_SUBTEST_5(check_tuple_size()); + CALL_SUBTEST_5(check_tuple_element()); + CALL_SUBTEST_6(check_storage_order_semantics()); + CALL_SUBTEST_6(check_storage_order_semantics()); +} + +#else + +EIGEN_DECLARE_TEST(structured_bindings) { + // Structured bindings require C++17. + VERIFY(true); +} + +#endif