[Bug c++/126483] New: Wrong active union member in constant evaluation of variable template initializers

speaktomarco at gmail dot com via Gcc-bugs <[email protected]>
Newsgroups gmane.comp.gcc.bugs
Message-ID <[email protected]/bugzilla/>
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126483

            Bug ID: 126483
           Summary: Wrong active union member in constant evaluation of
                    variable template initializers
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: speaktomarco at gmail dot com
  Target Milestone: ---

Created attachment 65168
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65168&action=edit
preprocessed reproducer, Boost.Multiprecision 1.91

Command line: g++ -std=gnu++26 -fsyntax-only bug_repro.ii
(same result with -std=c++20)

gcc rejects constexpr operations on a Boost.Multiprecision fixed cpp_int
wider than 128 bits when the value was initialized from a 128-bit integer,
but only when the code runs inside the initializer of a variable template.
clang 19 and 22 accept the same translation unit. Happens on current trunk:
https://godbolt.org/z/fojP5KPMT (gcc trunk/16.1/14.4 and clang 19.1). Tested
locally with gcc 16.1.0 and clang 22.1.8 (more details below).

Source before preprocessing (same as from compiler explorer link):

#include <boost/multiprecision/cpp_int.hpp>


namespace mp = boost::multiprecision;

using u129 = mp::number<
  mp::cpp_int_backend<129, 129, mp::unsigned_magnitude, mp::unchecked, void>,
  mp::et_off
>;

// FAILS: variable template initializer
template <int>
inline constexpr auto vt = [] (void) constexpr noexcept {
  u129 x = static_cast<unsigned __int128>(0);
  x += x;
  return x;
}();

// COMPILES: same body, plain constexpr variable
inline constexpr auto plain = [] (void) constexpr noexcept {
  u129 x = static_cast<unsigned __int128>(0);
  x += x;
  return x;
}();

static_assert(plain == 0);
static_assert(vt<0> == 0);

It is rejected with the following error:

/usr/local/include/boost/multiprecision/cpp_int/add_unsigned.hpp:32:16: error:
accessing 'boost::multiprecision::backends::cpp_int_base<129, 129,
boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked,
void, false>::data_type::m_data' member instead of active
'boost::multiprecision::backends::cpp_int_base<129, 129,
boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked,
void, false>::data_type::m_double_first_limb' member in constant expression
   32 |       result = static_cast<double_limb_type>(*a.limbs()) +
static_cast<double_limb_type>(*b.limbs());
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The union constructor involved (boost/multiprecision/cpp_int.hpp)
mem-initializes m_double_first_limb and then, under
std::is_constant_evaluated() (aliased as BOOST_MP_IS_CONST_EVALUATED), switches
the active member
to m_data through whole-union copy assignment:

      constexpr data_type(double_limb_type i) : m_double_first_limb(i)
      {
#ifndef BOOST_MP_NO_CONSTEXPR_DETECTION
         if (BOOST_MP_IS_CONST_EVALUATED(m_double_first_limb))
         {
            data_type t(static_cast<limb_type>(i & max_limb_value),
static_cast<limb_type>(i >> limb_bits));
            *this = t;
         }
#endif
      }

The active member that gcc reports always matches the one the
non-constant-evaluated branch of the constructor would initialize, even
though that branch never runs. The exact same body compiles as a plain
constexpr variable or a constexpr function (comment out the vt
static_assert on the reproducer and it passes). The same error happens with
'*this = t' replaced by 'std::construct_at' (maybe related to PR 102286),
and with std::is_constant_evaluated() replaced by 'if consteval'.
This is possibly related to the mce_* call handling from PR 119150.

Closest existing PRs I found: PR 92399, PR 110245, PR 117679.

I also could not reproduce this without boost (standalone unions with similar
structure compile fine), so the
preprocessed file is attached (xz-compressed).

Found while investigating
https://github.com/boostorg/multiprecision/issues/768.

gcc -v of the local gcc (trunk/14.4 also fails, see the compiler explorer link
above):

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-linux-gnu/16/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ./configure -v --build=x86_64-linux-gnu --disable-cet
--disable-multilib --disable-nls --disable-vtable-verify --disable-libatomic
--enable-checking=release --enable-clocale=gnu --enable-default-pie
--enable-gnu-unique-object --enable-languages=c,c++,fortran,lto
--enable-libstdcxx-debug --enable-libstdcxx-time=yes
--enable-libstdcxx-visibility --enable-link-serialization=2
--enable-linker-build-id --enable-lto --enable-plugin --enable-shared
--enable-symvers=gnu --enable-threads=posix --host=x86_64-linux-gnu
--prefix=/usr/local --program-suffix=-16 --target=x86_64-linux-gnu
--with-abi=m64 --with-arch-32=i686 --with-default-libstdcxx-abi=new
--with-gcc-major-version-only --with-tune=generic --without-cuda-driver
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 16.1.0 (GCC)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.