eigen_memset_helper should check if custom scalar needs initialized

Summary

Eigen 5.0.1 setZero() uses a memset-based zeroing path for trivially copyable scalars and ignores NumTraits<Scalar>::RequireInitialization. This bypasses scalar constructors and breaks types that require non-zero initialization (e.g., Stan’s var, or the custom scalar below), causing null pointer state and downstream segfaults. This is a regression vs Eigen 3.4.0 where setZero() used setConstant(Scalar(0)).

I think the answer is to just have eigen_memset_helper check if the expressions scalar type requires initialization.

template <typename Xpr>
struct eigen_memset_helper {
  static constexpr bool value =
      std::is_trivially_copyable<typename Xpr::Scalar>::value && eigen_fill_helper<Xpr>::value && (NumTraits<Xpr>::RequireInitialization == 0);
};

Changing that line makes the following test pass which currently fails on master.

EIGEN_DECLARE_TEST(zero_init) {
  using PlainType = Matrix<AnnoyingScalar, Dynamic, Dynamic, ColMajor>;
  PlainType m(4, 4);
  m.setZero();
  // This segfaults on master
  VERIFY_IS_CWISE_EQUAL(m, PlainType::Zero(4, 4));
}

Environment

Operating System : Linux Architecture : x64 Eigen Version : 5.0.1 Compiler Version : g++14.2.0 Compile Flags : -O2 -std=c++17

Minimal Example

https://godbolt.org/z/44qvTh1v5

  #include <Eigen/Dense>
  #include <iostream>
  #include <type_traits>

  struct NeedsInit {
    int* p;
    double v;
    static int sentinel;
    NeedsInit(double x = 0.0) : p(&sentinel), v(x) {}
    double get() const { return *p + v; }
  };
  int NeedsInit::sentinel = 1;

  namespace Eigen {
  template <>
  struct NumTraits<NeedsInit> : GenericNumTraits<NeedsInit> {
    typedef NeedsInit Real;
    typedef NeedsInit NonInteger;
    typedef NeedsInit Nested;
    enum {
      IsComplex = 0,
      IsInteger = 0,
      IsSigned = 1,
      RequireInitialization = 1,
      ReadCost = 1,
      AddCost = 1,
      MulCost = 1
    };
    static inline Real dummy_precision() { return Real(0); }
    static inline Real epsilon() { return Real(0); }
    static inline Real highest() { return Real(0); }
    static inline Real lowest() { return Real(0); }
  };
  }

  int main() {
    static_assert(std::is_trivially_copyable<NeedsInit>::value,
                  "NeedsInit should be trivially copyable");
    Eigen::Matrix<NeedsInit, -1, -1> m(4, 4);
    std::cout << "Init\n";
    m.setZero();  // Eigen 5.0.1 uses memset, zeroing p
    std::cout << "Zeroed\n";
    if (m(0, 0).p == nullptr) {
      std::cout << "FAIL: pointer nulled by setZero\n";
      return 1;
    }
    std::cout << m(0, 0).get() << "\n";
    return 0;
  }

Steps to reproduce the issue

See godbolt above

What is the current bug behavior?

When Eigen attempts to use the matrix that has not been set to zero then a segfault occurs.

What is the expected correct behavior?

No segfault

Anything else that might help

I tried finding a simpler solution to this, but without a patch for this I don't think Stan can update to Eigen 5.0.1 :(