SumOfLogarithms: give the Eigen unary functors a result_type, not a lambda - #58
Merged
lgueguen merged 1 commit intoSep 9, 2026
Conversation
Building bpp-phyl can fail inside Eigen with
Eigen/src/Core/Redux.h:204: error:
'bpp::ExtendedFloat::operator double() const' is private within this context
naming neither this file nor the real cause. The conversion operator is
private ON PURPOSE ("Necessary to keep this private to prevent misuse",
ExtendedFloat.h), with Eigen::internal::nullary_wrapper as its only friend --
so Eigen reaching it from Redux.h and CoreEvaluators.h means Eigen decided the
expression's Scalar is double and is converting our ExtendedFloat back.
It decided that in internal::result_of, which has three implementations chosen
by two macros that are mutually exclusive on the WRONG side:
EIGEN_HAS_STD_RESULT_OF needs EIGEN_COMP_CXXVER < 17
EIGEN_HAS_STD_INVOKE_RESULT needs EIGEN_COMP_CXXVER >= 17
AND EIGEN_MAX_CPP_VER >= 17
Compile at C++17 or later while EIGEN_MAX_CPP_VER is capped below 17 -- which a
distribution or a toolchain prefix can do through CXXFLAGS without bpp asking
-- and NEITHER is set. Eigen then falls back to the pre-C++11
unary_result_of_select (Eigen/src/Core/util/Meta.h:544), which looks for a
result_type member and, finding none on a lambda, DEFAULTS TO THE ARGUMENT
TYPE. The CwiseUnaryOp's Scalar becomes double instead of ExtendedFloat.
Measured against Eigen 3.4.0, g++ 14.2, this file:
-std=gnu++17 0 errors
-std=gnu++17 -DEIGEN_MAX_CPP_VER=14 the reported failure, verbatim
-std=gnu++17 -DEIGEN_MAX_CPP_VER=11 the same
A functor carrying result_type satisfies the legacy path as well as the two
modern ones, so the deduced Scalar is ExtendedFloat under every combination --
including any other route to "both macros unset", which is why this is fixed
here rather than by requiring a particular EIGEN_MAX_CPP_VER.
All four affected sites are converted. Two are in the ExtendedFloatEigen
overload (the ones that fail today); the other two are in the plain-Eigen
overload, which is not instantiated when VectorLik is an ExtendedFloatEigen and
so has the same latent defect without reporting it yet. They need two functors,
not one: the single-dependency branch of the plain-Eigen overload normalizes
SMALL while the others normalize.
After: 0 errors at C++14/17/20, with and without the cap. -Wall -Weffc++
-Wshadow -Wconversion warnings go 468 -> 464 (the remainder are Eigen's own
operator&&/|| -Weffc++ noise, which merely names the functor type).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mmokrejs
marked this pull request as ready for review
September 5, 2026 16:36
Member
|
Thanks for this precise development |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SumOfLogarithmspasses lambdas toEigen::unaryExpr. Eigen derives theresulting expression's
Scalarthroughinternal::result_of, and under onemacro combination it takes a pre-C++11 fallback that cannot see a lambda's
return type and defaults to the argument type instead. The expression's
scalar silently becomes
doublerather thanExtendedFloat, Eigen then triesto convert the results back through
ExtendedFloat::operator double()— whichis private by design — and the build fails deep inside Eigen's headers, naming
neither bpp-phyl nor the real cause.
This replaces the lambdas with small named functors carrying
typedef ExtendedFloat result_type, which is what that fallback looks for. Thededuced scalar is then
ExtendedFloaton every path, so the fix does not dependon which deduction implementation Eigen happens to select.
Behaviour is unchanged — the functors do exactly what the lambdas did. All four
unaryExprsites inSumOfLogarithmsare converted: two that fail today, andtwo that carry the same latent defect in an overload that is not currently
instantiated.
Symptom
First seen with GCC 16 and Eigen 3.4.0.
Why it is not what it looks like
The conversion operator is private deliberately — "Necessary to keep this
private to prevent misuse" (
ExtendedFloat.h), withEigen::internal::nullary_wrapperas its only friend. Eigen reaching it fromRedux.h/CoreEvaluators.htherefore means Eigen had already decided theexpression's scalar is
double. The instantiation backtrace says so directly:So the fix is not to widen the operator's access — that would defeat its
purpose. It is to stop Eigen deducing the wrong scalar.
Root cause
traits<CwiseUnaryOp>::Scalarcomes frominternal::result_of(
Eigen/src/Core/CwiseUnaryOp.h), which has three implementations selected bytwo macros that are mutually exclusive on the wrong side:
EIGEN_HAS_STD_RESULT_OFEIGEN_COMP_CXXVER < 17EIGEN_HAS_STD_INVOKE_RESULTEIGEN_COMP_CXXVER >= 17andEIGEN_MAX_CPP_VER >= 17CMakeLists.txtsetsCMAKE_CXX_STANDARD 17, soEIGEN_COMP_CXXVERis atleast 17 and
EIGEN_HAS_STD_RESULT_OFis 0. If anything capsEIGEN_MAX_CPP_VERbelow 17 — a distribution or a toolchain prefix may do thisthrough
CXXFLAGS, without bpp-phyl asking for it — thenEIGEN_HAS_STD_INVOKE_RESULTis 0 as well, and neither modern path isavailable.
Eigen falls back to the pre-C++11
unary_result_of_select(
Eigen/src/Core/util/Meta.h), which looks for aresult_typemember on thefunctor and, finding none on a lambda, defaults to the argument type:
The argument type is
double, so the whole expression becomes adoubleexpression.
Reproducer
Self-contained, against Eigen 3.4.0. The
-Dforces the macro state describedabove, so the failure can be reproduced on any compiler rather than only on the
toolchain that first showed it:
g++ -std=gnu++17 -DEIGEN_MAX_CPP_VER=14 -fsyntax-only \ -I src -I ../bpp-core/src -I ../bpp-seq/src -I /usr/include/eigen3 \ src/Bpp/Phyl/Likelihood/DataFlow/DataFlowCWiseComputing.cppThe macro state can also be inspected directly:
A healthy configuration shows
INVOKE_RESULT 1; the broken one showsRESULT_OF 0andINVOKE_RESULT 0together.The change
Two small functors, because the sites are not interchangeable — the
single-dependency branch of the plain-Eigen overload calls
normalize_small()while the others call
normalize():NormalizeToExtendedFloatNormalizeSmallToExtendedFloatConverted sites, all in
SumOfLogarithms:ExtendedFloatEigenoverload — these are the ones that fail today;VectorLikisan
ExtendedFloatEigen, so it carries the same latent defect withoutreporting it.
Fixing it here rather than by requiring a particular
EIGEN_MAX_CPP_VERwasdeliberate: the value is set outside the project, and a functor with
result_typeis correct under every route into that fallback, not just the oneobserved.
Verification
DataFlowCWiseComputing.cpp, Eigen 3.4.0, g++ 14.2:-std=gnu++17-std=gnu++17 -DEIGEN_MAX_CPP_VER=14-std=gnu++17 -DEIGEN_MAX_CPP_VER=11-std=gnu++14-std=gnu++20Separately confirmed on the GCC 16 toolchain where the failure was first
observed: that build previously stopped at this file, and now compiles it and
proceeds through the rest of the library.
-Wall -Weffc++ -Wshadow -Wconversionon that translation unit: 468 → 464warnings, none added. The remainder is Eigen's own
operator&&/operator||-Weffc++noise, which merely names the functor type in the instantiation.Branched from
masterat5a57563d, its tip at the time.Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
🤖 Generated with Claude Code