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)

Reply via email to