Skip to content

Internal cmath headers fail to compile when boost::log is in scope (unqualified log resolves to namespace) #1384

@acko1980

Description

@acko1980

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)

Metadata

Metadata

Assignees

Labels

BugSomething isn't workingrequired

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions