Summary
Several internal cmath headers in boost::decimal call log(...) (and log1p, log10) unqualified inside template bodies. In any TU where boost::log (the Boost.Log namespace, declared in <boost/log/detail/config.hpp>) is brought into scope before <boost/decimal/cmath.hpp> is parsed, unqualified lookup at template-definition time finds the namespace boost::log before boost::decimal::log (which is not yet declared at the point those headers run within the umbrella's include order), and the compiler errors out:
- Clang:
error: unexpected namespace name 'log': expected expression
- GCC:
error: expected primary-expression before '(' token [-Wtemplate-body]
Reproduces on both Apple Clang 21.0.0 and GCC 15.2.0; this is standard-conforming behaviour of two-phase template lookup, not a compiler bug.
Affected headers (v6.0.1)
All call log / log10 / log1p unqualified inside template bodies. The seven failing sites match exactly across both Clang and GCC:
| Header |
Line |
boost/decimal/detail/cmath/acosh.hpp |
79, 93 |
boost/decimal/detail/cmath/asinh.hpp |
55, 60 |
boost/decimal/detail/cmath/atanh.hpp |
69 |
boost/decimal/detail/cmath/impl/ellint_impl.hpp |
80 |
boost/decimal/detail/cmath/log.hpp |
56 (inside log_impl, before boost::decimal::log is declared at line 80 of the same header) |
These are all included via the <boost/decimal/cmath.hpp> umbrella before detail/cmath/log.hpp is itself included, so even within the umbrella's own parse, boost::decimal::log isn't visible when the dependent calls are first encountered. Two-phase lookup defers ADL on dependent arguments to instantiation, but the unqualified function-name lookup at template-definition time succeeds against boost::log (a namespace), which is invalid in expression context, and parsing fails.
Reproducer
// repro.cpp
#include <boost/log/trivial.hpp> // brings boost::log namespace into scope
#include <boost/decimal/cmath.hpp> // fails to parse
int main() { return 0; }
Verified on
Apple Clang 21.0.0 (clang-2100.0.123.102, arm64-apple-darwin25.4.0):
$ clang++ -std=c++17 -c -I<boost-1.90> -I<boost-decimal-6.0.1> repro.cpp
error: unexpected namespace name 'log': expected expression (acosh.hpp:79, acosh.hpp:93,
asinh.hpp:55, asinh.hpp:60, atanh.hpp:69, ellint_impl.hpp:80, log.hpp:56)
7 errors generated.
Homebrew GCC 15.2.0 (g++-15, same arm64 host):
$ g++-15 -std=c++17 -c -I<boost-1.90> -I<boost-decimal-6.0.1> repro.cpp
error: expected primary-expression before '(' token [-Wtemplate-body] (same 7 sites)
Reproduces independently of whether the program ever instantiates a decimal transcendental — the failure is at parse time. Same source locations under both toolchains confirms this is an upstream Boost.Decimal source issue, not a compiler-frontend quirk.
Why it doesn't surface universally
Without boost::log ever entering scope (most isolated test setups don't include any Boost.Log header), unqualified lookup at definition time finds nothing for log, the call is treated as ADL-only, and ADL at instantiation correctly resolves boost::decimal::log. The bug only manifests when a real-world TU mixes Boost.Log + Boost.Decimal.
Suggested fix
Three options, in roughly increasing order of invasiveness:
1. Forward-declare boost::decimal::log in an early header (e.g. fwd.hpp, or a new detail/cmath/log_fwd.hpp). Once boost::decimal::log is declared anywhere in boost::decimal before the affected templates are parsed, unqualified lookup at template-definition time finds it (closer scope) and never escalates to boost::log (outer scope). Smallest change — no impl-body edits, no umbrella reorder, works regardless of whether downstream consumes the umbrella or individual subheaders. Caveat: log's declaration uses BOOST_DECIMAL_REQUIRES, so the forward declaration needs the matching constraint, which means the forward-decl header must transitively reach detail/type_traits.hpp (whether that's via fwd.hpp or a small new header is your call). Note that log is the only name that collides — Boost.Log defines no log1p/log10/etc. namespace — so a single forward declaration suffices.
2. Reorder <boost/decimal/cmath.hpp> so detail/cmath/log.hpp is included before any header that references log (currently acosh/asinh/atanh/ellint_*). Fixes the umbrella case but doesn't help anyone who pulls the affected detail headers individually in a different order.
3. Qualify the internal calls at every callsite. e.g. in detail/cmath/atanh.hpp:69:
- result = (log((one + xx) / (one - xx)) / 2);
+ result = (boost::decimal::log((one + xx) / (one - xx)) / 2);
Most robust against future namespace collisions (e.g. if Boost ever adds another single-noun namespace that clashes with another boost::decimal cmath function), but touches more sites.
Workaround for downstream
Anyone who only needs a subset of <boost/decimal/cmath.hpp> (e.g. abs/ceil/floor/rint/quantize) can include the relevant detail/cmath/*.hpp subheaders directly and skip the umbrella, avoiding the affected files entirely. That's what we did downstream.
Environment
- Boost.Decimal: v6.0.1
- Compilers:
- Apple Clang 21.0.0 (
clang-2100.0.123.102)
- Homebrew GCC 15.2.0 (
g++-15)
- Standard: C++17
- Stdlib: libc++ (under Clang), libstdc++ (under GCC)
- Boost: 1.90.0 (with Boost.Log)
- Platform: macOS arm64 (compiler/stdlib-driven, not platform-specific)
Summary
Several internal
cmathheaders inboost::decimalcalllog(...)(andlog1p,log10) unqualified inside template bodies. In any TU whereboost::log(the Boost.Log namespace, declared in<boost/log/detail/config.hpp>) is brought into scope before<boost/decimal/cmath.hpp>is parsed, unqualified lookup at template-definition time finds the namespaceboost::logbeforeboost::decimal::log(which is not yet declared at the point those headers run within the umbrella's include order), and the compiler errors out:error: unexpected namespace name 'log': expected expressionerror: expected primary-expression before '(' token [-Wtemplate-body]Reproduces on both Apple Clang 21.0.0 and GCC 15.2.0; this is standard-conforming behaviour of two-phase template lookup, not a compiler bug.
Affected headers (v6.0.1)
All call
log/log10/log1punqualified inside template bodies. The seven failing sites match exactly across both Clang and GCC:boost/decimal/detail/cmath/acosh.hppboost/decimal/detail/cmath/asinh.hppboost/decimal/detail/cmath/atanh.hppboost/decimal/detail/cmath/impl/ellint_impl.hppboost/decimal/detail/cmath/log.hpplog_impl, beforeboost::decimal::logis declared at line 80 of the same header)These are all included via the
<boost/decimal/cmath.hpp>umbrella beforedetail/cmath/log.hppis itself included, so even within the umbrella's own parse,boost::decimal::logisn't visible when the dependent calls are first encountered. Two-phase lookup defers ADL on dependent arguments to instantiation, but the unqualified function-name lookup at template-definition time succeeds againstboost::log(a namespace), which is invalid in expression context, and parsing fails.Reproducer
Verified on
Apple Clang 21.0.0 (
clang-2100.0.123.102, arm64-apple-darwin25.4.0):Homebrew GCC 15.2.0 (
g++-15, same arm64 host):Reproduces independently of whether the program ever instantiates a decimal transcendental — the failure is at parse time. Same source locations under both toolchains confirms this is an upstream Boost.Decimal source issue, not a compiler-frontend quirk.
Why it doesn't surface universally
Without
boost::logever entering scope (most isolated test setups don't include any Boost.Log header), unqualified lookup at definition time finds nothing forlog, the call is treated as ADL-only, and ADL at instantiation correctly resolvesboost::decimal::log. The bug only manifests when a real-world TU mixes Boost.Log + Boost.Decimal.Suggested fix
Three options, in roughly increasing order of invasiveness:
1. Forward-declare
boost::decimal::login an early header (e.g.fwd.hpp, or a newdetail/cmath/log_fwd.hpp). Onceboost::decimal::logis declared anywhere inboost::decimalbefore the affected templates are parsed, unqualified lookup at template-definition time finds it (closer scope) and never escalates toboost::log(outer scope). Smallest change — no impl-body edits, no umbrella reorder, works regardless of whether downstream consumes the umbrella or individual subheaders. Caveat:log's declaration usesBOOST_DECIMAL_REQUIRES, so the forward declaration needs the matching constraint, which means the forward-decl header must transitively reachdetail/type_traits.hpp(whether that's viafwd.hppor a small new header is your call). Note thatlogis the only name that collides — Boost.Log defines nolog1p/log10/etc. namespace — so a single forward declaration suffices.2. Reorder
<boost/decimal/cmath.hpp>sodetail/cmath/log.hppis included before any header that referenceslog(currentlyacosh/asinh/atanh/ellint_*). Fixes the umbrella case but doesn't help anyone who pulls the affected detail headers individually in a different order.3. Qualify the internal calls at every callsite. e.g. in
detail/cmath/atanh.hpp:69:Most robust against future namespace collisions (e.g. if Boost ever adds another single-noun namespace that clashes with another
boost::decimalcmath function), but touches more sites.Workaround for downstream
Anyone who only needs a subset of
<boost/decimal/cmath.hpp>(e.g.abs/ceil/floor/rint/quantize) can include the relevantdetail/cmath/*.hppsubheaders directly and skip the umbrella, avoiding the affected files entirely. That's what we did downstream.Environment
clang-2100.0.123.102)g++-15)