From 96e91378a210cd07de7065480a903cbe62ba74b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Geis?= Date: Fri, 28 Aug 2026 13:48:11 +0900 Subject: [PATCH] fix `CountFieldsHelper` when a field is `std::optional` `CountFieldsHelper` inferred the number of fields of a struct by aggregate-initializing it with as many `any` values as possible, `any` being a type that implicitly converts to anything. If `T` contains a `std::optional` field, Clang will instantiate its constructor when evaluating the initializer (as its converting constructor is `constexpr`). For `Eigen::Matrix`, this is a problem as it has `explicit Matrix(const T&)`, whose implementation cannot pick an overload that matches `any`. We can use `std::is_constructible` instead, which won't lead to that instantiation. It also prevents brace elision, which makes the count exact for structs holding a nested aggregate. We have to make sure not to convert to `T` itself, or a single argument would select `T`'s copy constructor and cut the recursive search short. --- include/rfl/internal/cpp20/num_fields.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/include/rfl/internal/cpp20/num_fields.hpp b/include/rfl/internal/cpp20/num_fields.hpp index 3864336e..42c6c055 100644 --- a/include/rfl/internal/cpp20/num_fields.hpp +++ b/include/rfl/internal/cpp20/num_fields.hpp @@ -78,12 +78,22 @@ struct any { constexpr operator T() const noexcept; }; +/// Like `any`, but never converts to `T` itself, so that a single argument cannot select `T`'s +/// copy or move constructor instead of aggregate-initializing its first field. +template +struct any_but { + any_but(std::size_t); + template + requires(!std::is_same_v, std::remove_cv_t>) + constexpr operator U() const noexcept; +}; + template struct CountFieldsHelper { template static consteval bool constructible() { return [](std::index_sequence) { - return requires { T{any(is)...}; }; + return std::is_constructible_v(is))...>; }(std::make_index_sequence()); }