Add C++17 structured bindings support for fixed-size Matrix and Array

libeigen/eigen!2336

Closes #2247
diff --git a/Eigen/Core b/Eigen/Core
index 424396f..2fec8f3 100644
--- a/Eigen/Core
+++ b/Eigen/Core
@@ -361,6 +361,7 @@
 #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 0000000..073f10e
--- /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 <tuple>
+#include <type_traits>
+
+// 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<MatrixXd>.
+template <typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+struct tuple_size<Eigen::Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>>
+    : std::integral_constant<size_t, static_cast<size_t>((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 <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+struct tuple_element<Idx_, Eigen::Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>> {
+  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<size_t>(Rows_ * Cols_), "Index out of range.");
+  using type = Scalar_;
+};
+
+// std::tuple_size for fixed-size Array. See note on Matrix specialization above.
+template <typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+struct tuple_size<Eigen::Array<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>>
+    : std::integral_constant<size_t, static_cast<size_t>((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 <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+struct tuple_element<Idx_, Eigen::Array<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>> {
+  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<size_t>(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<I> level. coeffRef(Index) is
+// linear in storage order, so permitting both orientations for 2D types would
+// silently flip the binding order between Matrix<T,R,C> and Matrix<T,R,C,RowMajor>.
+// 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<Idx_> free functions for Matrix.
+template <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_& get(
+    Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>& m) noexcept {
+  static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types.");
+  static_assert(Idx_ < static_cast<size_t>(Rows_ * Cols_), "Index out of range.");
+  EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_);
+  return m.coeffRef(static_cast<Index>(Idx_));
+}
+
+template <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const Scalar_& get(
+    const Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>& m) noexcept {
+  static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types.");
+  static_assert(Idx_ < static_cast<size_t>(Rows_ * Cols_), "Index out of range.");
+  EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_);
+  return m.coeffRef(static_cast<Index>(Idx_));
+}
+
+template <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_&& get(
+    Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>&& m) noexcept {
+  static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types.");
+  static_assert(Idx_ < static_cast<size_t>(Rows_ * Cols_), "Index out of range.");
+  EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_);
+  return std::move(m.coeffRef(static_cast<Index>(Idx_)));
+}
+
+// get<Idx_> free functions for Array.
+template <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_& get(
+    Array<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>& a) noexcept {
+  static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types.");
+  static_assert(Idx_ < static_cast<size_t>(Rows_ * Cols_), "Index out of range.");
+  EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_);
+  return a.coeffRef(static_cast<Index>(Idx_));
+}
+
+template <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const Scalar_& get(
+    const Array<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>& a) noexcept {
+  static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types.");
+  static_assert(Idx_ < static_cast<size_t>(Rows_ * Cols_), "Index out of range.");
+  EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_);
+  return a.coeffRef(static_cast<Index>(Idx_));
+}
+
+template <size_t Idx_, typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
+EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar_&& get(
+    Array<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_>&& a) noexcept {
+  static_assert(Rows_ != Dynamic && Cols_ != Dynamic, "Structured bindings require fixed-size Eigen types.");
+  static_assert(Idx_ < static_cast<size_t>(Rows_ * Cols_), "Index out of range.");
+  EIGEN_STRUCTURED_BINDINGS_ASSERT_COL_MAJOR(Rows_, Cols_, Options_);
+  return std::move(a.coeffRef(static_cast<Index>(Idx_)));
+}
+
+}  // namespace Eigen
+
+#endif  // C++17
+
+#endif  // EIGEN_STRUCTURED_BINDINGS_H
diff --git a/failtest/CMakeLists.txt b/failtest/CMakeLists.txt
index 107c9e2..0e55c0b 100644
--- a/failtest/CMakeLists.txt
+++ b/failtest/CMakeLists.txt
@@ -66,3 +66,20 @@
 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 0000000..1986e5c
--- /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 <tuple>
+
+int main() { return static_cast<int>(std::tuple_size<Eigen::Array<double, ROWS, COLS>>::value); }
diff --git a/failtest/structured_bindings_dynamic_matrix.cpp b/failtest/structured_bindings_dynamic_matrix.cpp
new file mode 100644
index 0000000..b571112
--- /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 <tuple>
+
+int main() { return static_cast<int>(std::tuple_size<Eigen::Matrix<double, ROWS, COLS>>::value); }
diff --git a/failtest/structured_bindings_rowmajor.cpp b/failtest/structured_bindings_rowmajor.cpp
new file mode 100644
index 0000000..a3c502a
--- /dev/null
+++ b/failtest/structured_bindings_rowmajor.cpp
@@ -0,0 +1,17 @@
+#include "../Eigen/Core"
+
+// Reproduces the "storage-order footgun" on !2336: get<I>() 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<double, 2, 2, STORAGE_ORDER> m;
+  m << 1, 2, 3, 4;
+  return static_cast<int>(Eigen::get<0>(m));
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index b362f1f..8d551b0 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -338,6 +338,7 @@
 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 0000000..e497884
--- /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 <typename Scalar>
+void check_vector_bindings() {
+  // Vector2
+  {
+    Matrix<Scalar, 2, 1> v;
+    v << Scalar(1), Scalar(2);
+    auto [x, y] = v;
+    VERIFY_IS_EQUAL(x, Scalar(1));
+    VERIFY_IS_EQUAL(y, Scalar(2));
+  }
+
+  // Vector3
+  {
+    Matrix<Scalar, 3, 1> 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<Scalar, 4, 1> 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<Scalar, 1, 1> s;
+    s << Scalar(42);
+    auto [val] = s;
+    VERIFY_IS_EQUAL(val, Scalar(42));
+  }
+
+  // RowVector
+  {
+    Matrix<Scalar, 1, 3> 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 <typename Scalar>
+void check_array_bindings() {
+  // Array3
+  {
+    Array<Scalar, 3, 1> 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<Scalar, 2, 1> a;
+    a << Scalar(10), Scalar(20);
+    auto [x, y] = a;
+    VERIFY_IS_EQUAL(x, Scalar(10));
+    VERIFY_IS_EQUAL(y, Scalar(20));
+  }
+}
+
+template <typename Scalar>
+void check_reference_bindings() {
+  // Mutable reference binding
+  {
+    Matrix<Scalar, 3, 1> 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<Scalar, 3, 1> 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<Scalar, 2, 1> 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 <typename Scalar>
+void check_matrix_bindings() {
+  // 2x2 matrix (column-major order)
+  {
+    Matrix<Scalar, 2, 2> 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 <typename Scalar>
+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<Scalar, 1, 3> 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<Scalar, 3, 1> 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<Scalar, 2, 2, ColMajor> 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<Vector2d>::value == 2));
+  STATIC_CHECK((std::tuple_size<Vector3f>::value == 3));
+  STATIC_CHECK((std::tuple_size<Vector4i>::value == 4));
+  STATIC_CHECK((std::tuple_size<Matrix2d>::value == 4));
+  STATIC_CHECK((std::tuple_size<Matrix3f>::value == 9));
+  STATIC_CHECK((std::tuple_size<Array3i>::value == 3));
+  STATIC_CHECK((std::tuple_size<Array<double, 2, 1>>::value == 2));
+  STATIC_CHECK((std::tuple_size<Matrix<float, 1, 1>>::value == 1));
+  STATIC_CHECK((std::tuple_size<RowVector3d>::value == 3));
+}
+
+void check_tuple_element() {
+  STATIC_CHECK((std::is_same<std::tuple_element_t<0, Vector3d>, double>::value));
+  STATIC_CHECK((std::is_same<std::tuple_element_t<1, Vector3f>, float>::value));
+  STATIC_CHECK((std::is_same<std::tuple_element_t<2, Vector4i>, int>::value));
+  STATIC_CHECK((std::is_same<std::tuple_element_t<0, Array3i>, int>::value));
+}
+
+EIGEN_DECLARE_TEST(structured_bindings) {
+  CALL_SUBTEST_1(check_vector_bindings<double>());
+  CALL_SUBTEST_1(check_vector_bindings<float>());
+  CALL_SUBTEST_1(check_vector_bindings<int>());
+  CALL_SUBTEST_2(check_array_bindings<double>());
+  CALL_SUBTEST_2(check_array_bindings<int>());
+  CALL_SUBTEST_3(check_reference_bindings<double>());
+  CALL_SUBTEST_3(check_reference_bindings<float>());
+  CALL_SUBTEST_4(check_matrix_bindings<double>());
+  CALL_SUBTEST_4(check_matrix_bindings<int>());
+  CALL_SUBTEST_5(check_tuple_size());
+  CALL_SUBTEST_5(check_tuple_element());
+  CALL_SUBTEST_6(check_storage_order_semantics<double>());
+  CALL_SUBTEST_6(check_storage_order_semantics<int>());
+}
+
+#else
+
+EIGEN_DECLARE_TEST(structured_bindings) {
+  // Structured bindings require C++17.
+  VERIFY(true);
+}
+
+#endif